mirror of
https://github.com/tauri-apps/cef-rs.git
synced 2026-01-31 00:55:21 +01:00
feat(test): port tests/shared library from CEF
feat: unify bundle utilities under cef build-util feature feat: reenable sandbox support in cefsimple feat: add MainMenu.xib on mac fix: bypass tryToTerminateApplication for Command+Q fix: enable much smaller linux release builds
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ cargo-sources.json
|
||||
Cargo.lock
|
||||
|
||||
/.vscode/
|
||||
**/.DS_Store
|
||||
|
||||
@@ -23,18 +23,20 @@ authors = [
|
||||
repository = "https://github.com/tauri-apps/cef-rs"
|
||||
|
||||
[workspace.dependencies]
|
||||
cef = { path = "cef" }
|
||||
cef = { path = "cef", default-features = false }
|
||||
cef-dll-sys = { version = "143.4.0", path = "sys" }
|
||||
download-cef = { version = "2.2", path = "download-cef" }
|
||||
|
||||
anyhow = "1"
|
||||
ash = "0.38"
|
||||
bindgen = "0.72"
|
||||
cargo_metadata = "0.23.1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
cmake = "0.1.52"
|
||||
convert_case = "0.10"
|
||||
git-cliff = "2"
|
||||
git-cliff-core = "2"
|
||||
glib = "0.21"
|
||||
io-surface = "0.16"
|
||||
libc = "0.2"
|
||||
libloading = "0.9"
|
||||
@@ -42,7 +44,8 @@ metal = "0.32"
|
||||
objc = "0.2"
|
||||
objc2 = "0.6.3"
|
||||
objc2-app-kit = { version = "0.3.2", default-features = false }
|
||||
objc2-io-surface = "0.3"
|
||||
objc2-foundation = "0.3.2"
|
||||
objc2-io-surface = "0.3.2"
|
||||
plist = "1"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
@@ -55,6 +58,8 @@ thiserror = "2"
|
||||
toml_edit = "0.24"
|
||||
tracing = "0.1"
|
||||
wgpu = "27"
|
||||
winres = "0.1"
|
||||
x11-dl = "2"
|
||||
|
||||
[workspace.dependencies.windows-sys]
|
||||
version = "0.61"
|
||||
|
||||
13
README.md
13
README.md
@@ -52,6 +52,11 @@ $env:PATH="$env:PATH;$env:CEF_PATH"
|
||||
|
||||
### Run the `cefsimple` Example
|
||||
|
||||
This command should work with each platform:
|
||||
```sh
|
||||
cargo run --bin bundle-cef-app -- cefsimple -o target/bundle
|
||||
```
|
||||
|
||||
#### Linux
|
||||
|
||||
```sh
|
||||
@@ -61,15 +66,15 @@ cargo run --bin cefsimple
|
||||
#### macOS
|
||||
|
||||
```sh
|
||||
cargo run --bin bundle_cefsimple
|
||||
open target/debug/cefsimple.app
|
||||
cargo run --bin bundle-cef-app -- cefsimple -o target/bundle
|
||||
open target/bundle/cefsimple.app
|
||||
```
|
||||
|
||||
#### Windows (using PowerShell)
|
||||
|
||||
```pwsh
|
||||
cp ./examples/cefsimple/src/win/cefsimple.exe.manifest ./target/debug/
|
||||
cargo run --bin cefsimple
|
||||
cargo run --bin bundle-cef-app -- cefsimple -o ./target/bundle
|
||||
./target/bundle/cefsimple.exe
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -8,23 +8,44 @@ license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
|
||||
[[bin]]
|
||||
name = "bundle-cef-app"
|
||||
|
||||
[features]
|
||||
default = ["sandbox"]
|
||||
default = ["sandbox", "build-util"]
|
||||
dox = ["cef-dll-sys/dox"]
|
||||
sandbox = ["cef-dll-sys/sandbox"]
|
||||
|
||||
# Unified texture import system for CEF hardware acceleration
|
||||
accelerated_osr = [
|
||||
"ash",
|
||||
"libc",
|
||||
"objc",
|
||||
"objc2-io-surface",
|
||||
"metal",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"wgpu",
|
||||
"windows",
|
||||
"libc",
|
||||
"ash",
|
||||
"tracing",
|
||||
"thiserror",
|
||||
"objc2-io-surface",
|
||||
"objc",
|
||||
"metal"
|
||||
]
|
||||
|
||||
# Build utilities, including the bundle-cef-app tool.
|
||||
build-util = [
|
||||
"anyhow",
|
||||
"cargo_metadata",
|
||||
"clap",
|
||||
"plist",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
# Linux X11 support in bundle-cef-app.
|
||||
linux-x11 = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["dox"]
|
||||
|
||||
@@ -37,6 +58,13 @@ thiserror = { workspace = true, optional = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
wgpu = { workspace = true, optional = true }
|
||||
|
||||
# bundle
|
||||
anyhow = { workspace = true, optional = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
cargo_metadata = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys.workspace = true
|
||||
windows = { workspace = true, optional = true }
|
||||
@@ -47,6 +75,8 @@ objc2.workspace = true
|
||||
objc2-io-surface = { workspace = true, optional = true }
|
||||
objc = { workspace = true, optional = true }
|
||||
metal = { workspace = true, optional = true }
|
||||
plist = { workspace = true, optional = true }
|
||||
semver = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libc = { workspace = true, optional = true }
|
||||
|
||||
@@ -56,9 +56,7 @@ impl Args {
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
pub fn as_cmd_line(&self) -> Option<CommandLine> {
|
||||
let Some(cmd_line) = command_line_create() else {
|
||||
return None;
|
||||
};
|
||||
let cmd_line = command_line_create()?;
|
||||
cmd_line.init_from_argv(self.as_main_args().argc, self.as_main_args().argv.cast());
|
||||
Some(cmd_line)
|
||||
}
|
||||
@@ -81,3 +79,12 @@ impl Args {
|
||||
cmd_line
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MainArgs> for Args {
|
||||
fn from(main_args: MainArgs) -> Self {
|
||||
Args {
|
||||
main_args,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
cef/src/bin/bundle-cef-app/linux.rs
Normal file
26
cef/src/bin/bundle-cef-app/linux.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use cef::build_util::linux::*;
|
||||
use clap::Parser;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about, long_about = None)]
|
||||
struct Args {
|
||||
name: String,
|
||||
#[arg(long, default_value_t = false)]
|
||||
release: bool,
|
||||
#[arg(short, long)]
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let output = match args.output {
|
||||
Some(output) => PathBuf::from(output),
|
||||
None => env::current_dir()?,
|
||||
};
|
||||
|
||||
let bundle_path = build_bundle(output.as_path(), &args.name, args.release)?;
|
||||
let bundle_path = bundle_path.display();
|
||||
println!("Run the app from {bundle_path}");
|
||||
Ok(())
|
||||
}
|
||||
46
cef/src/bin/bundle-cef-app/mac.rs
Normal file
46
cef/src/bin/bundle-cef-app/mac.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use cef::build_util::mac::*;
|
||||
use clap::Parser;
|
||||
use semver::Version;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about, long_about = None)]
|
||||
struct Args {
|
||||
name: String,
|
||||
#[arg(short, long)]
|
||||
output: Option<String>,
|
||||
#[arg(short, long)]
|
||||
identifier: Option<String>,
|
||||
#[arg(short, long)]
|
||||
display_name: Option<String>,
|
||||
#[arg(short, long, default_value = "English")]
|
||||
region: String,
|
||||
#[arg(short, long, default_value = "1.0.0")]
|
||||
version: String,
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let output = match args.output {
|
||||
Some(output) => PathBuf::from(output),
|
||||
None => env::current_dir()?,
|
||||
};
|
||||
let identifier = args
|
||||
.identifier
|
||||
.unwrap_or_else(|| format!("apps.tauri.cef-rs.{}", args.name));
|
||||
let display_name = args.display_name.unwrap_or_else(|| args.name.clone());
|
||||
let version = Version::parse(&args.version)?;
|
||||
|
||||
let bundle_info = BundleInfo {
|
||||
name: args.name.clone(),
|
||||
identifier,
|
||||
display_name,
|
||||
development_region: args.region,
|
||||
version,
|
||||
};
|
||||
|
||||
let bundle_path = build_bundle(output.as_path(), &args.name, bundle_info)?;
|
||||
let bundle_path = bundle_path.display();
|
||||
println!("Run the app from {bundle_path}");
|
||||
Ok(())
|
||||
}
|
||||
29
cef/src/bin/bundle-cef-app/main.rs
Normal file
29
cef/src/bin/bundle-cef-app/main.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#[cfg(not(feature = "build-util"))]
|
||||
fn main() {
|
||||
let command = std::env::current_exe().expect("Failed to get current executable path");
|
||||
eprintln!("Disabled: {command:?} was compiled without the build-util feature.");
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "build-util", target_os = "macos"))]
|
||||
mod mac;
|
||||
|
||||
#[cfg(all(feature = "build-util", target_os = "macos"))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
mac::main()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "build-util", target_os = "linux"))]
|
||||
mod linux;
|
||||
|
||||
#[cfg(all(feature = "build-util", target_os = "linux"))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
linux::main()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "build-util", target_os = "windows"))]
|
||||
mod win;
|
||||
|
||||
#[cfg(all(feature = "build-util", target_os = "windows"))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
win::main()
|
||||
}
|
||||
26
cef/src/bin/bundle-cef-app/win.rs
Normal file
26
cef/src/bin/bundle-cef-app/win.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use cef::build_util::win::*;
|
||||
use clap::Parser;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about, long_about = None)]
|
||||
struct Args {
|
||||
name: String,
|
||||
#[arg(long, default_value_t = false)]
|
||||
release: bool,
|
||||
#[arg(short, long)]
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let output = match args.output {
|
||||
Some(output) => PathBuf::from(output),
|
||||
None => env::current_dir()?,
|
||||
};
|
||||
|
||||
let bundle_path = build_bundle(output.as_path(), &args.name, args.release)?;
|
||||
let bundle_path = bundle_path.display();
|
||||
println!("Run the app from {bundle_path}");
|
||||
Ok(())
|
||||
}
|
||||
77
cef/src/build_util/linux.rs
Normal file
77
cef/src/build_util/linux.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("I/O error: {0:?}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Metadata error: {0:?}")]
|
||||
Metadata(#[from] super::metadata::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-linux
|
||||
pub fn bundle(app_path: &Path, target_path: &Path, executable_name: &str) -> Result<PathBuf> {
|
||||
let cef_path = cef_dll_sys::get_cef_dir().unwrap();
|
||||
copy_directory(&cef_path, &app_path)?;
|
||||
|
||||
const LOCALES_DIR: &str = "locales";
|
||||
copy_directory(&cef_path.join(LOCALES_DIR), &app_path.join(LOCALES_DIR))?;
|
||||
|
||||
copy_app(app_path, target_path, executable_name)
|
||||
}
|
||||
|
||||
/// Similar to [`bundle`], but this will invoke `cargo build` to build the executable target.
|
||||
pub fn build_bundle(app_path: &Path, executable_name: &str, release: bool) -> Result<PathBuf> {
|
||||
let cargo_metadata = super::metadata::get_cargo_metadata()?;
|
||||
let target_path =
|
||||
cargo_metadata
|
||||
.target_directory()
|
||||
.join(if release { "release" } else { "debug" });
|
||||
|
||||
cargo_build(executable_name, release)?;
|
||||
|
||||
bundle(app_path, &target_path, executable_name)
|
||||
}
|
||||
|
||||
fn copy_app(app_path: &Path, target_path: &Path, executable_name: &str) -> Result<PathBuf> {
|
||||
let executable_path = app_path.join(executable_name);
|
||||
let target_executable = target_path.join(executable_name);
|
||||
fs::copy(&target_executable, &executable_path)?;
|
||||
Ok(executable_path)
|
||||
}
|
||||
|
||||
fn copy_directory(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
fs::create_dir_all(dst)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let dst_path = dst.join(entry.file_name());
|
||||
if entry.file_type()?.is_file() {
|
||||
fs::copy(entry.path(), &dst_path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cargo_build(name: &str, release: bool) -> Result<()> {
|
||||
println!("Building {name}...");
|
||||
|
||||
let mut args = vec!["build"];
|
||||
if release {
|
||||
args.push("--release");
|
||||
}
|
||||
#[cfg(feature = "linux-x11")]
|
||||
args.extend(["-F", "linux-x11"]);
|
||||
args.extend(["--bin", name]);
|
||||
|
||||
let status = Command::new(super::cargo_path()).args(args).status()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::Interrupted).into())
|
||||
}
|
||||
}
|
||||
342
cef/src/build_util/mac.rs
Normal file
342
cef/src/build_util/mac.rs
Normal file
@@ -0,0 +1,342 @@
|
||||
use semver::Version;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("I/O error: {0:?}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Metadata error: {0:?}")]
|
||||
Metadata(#[from] super::metadata::Error),
|
||||
#[error("Plist error: {0:?}")]
|
||||
Plist(#[from] plist::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Common bundle information that is shared in the [Info.plist](https://developer.apple.com/documentation/bundleresources/information-property-list) files.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct BundleInfo {
|
||||
/// [CFBundleName](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundlename)
|
||||
#[serde(rename = "CFBundleName")]
|
||||
pub name: String,
|
||||
/// [CFBundleIdentifier](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleidentifier)
|
||||
#[serde(rename = "CFBundleIdentifier")]
|
||||
pub identifier: String,
|
||||
/// [CFBundleDisplayName](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundledisplayname)
|
||||
#[serde(rename = "CFBundleDisplayName")]
|
||||
pub display_name: String,
|
||||
/// [CFBundleDevelopmentRegion](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundledevelopmentregion)
|
||||
#[serde(rename = "CFBundleDevelopmentRegion")]
|
||||
pub development_region: String,
|
||||
/// [CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleversion)
|
||||
#[serde(rename = "CFBundleVersion", serialize_with = "serialize_version")]
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
impl BundleInfo {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
identifier: &str,
|
||||
display_name: &str,
|
||||
development_region: &str,
|
||||
version: Version,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_owned(),
|
||||
identifier: identifier.to_owned(),
|
||||
display_name: display_name.to_owned(),
|
||||
development_region: development_region.to_owned(),
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-macos
|
||||
pub fn bundle(
|
||||
app_path: &Path,
|
||||
target_path: &Path,
|
||||
executable_name: &str,
|
||||
helper_name: &str,
|
||||
resources_path: Option<PathBuf>,
|
||||
bundle_info: BundleInfo,
|
||||
) -> Result<PathBuf> {
|
||||
let main_app_path = create_app(
|
||||
app_path,
|
||||
executable_name,
|
||||
false,
|
||||
resources_path.as_deref(),
|
||||
bundle_info.clone(),
|
||||
&target_path.join(executable_name),
|
||||
)?;
|
||||
let cef_path = cef_dll_sys::get_cef_dir().unwrap();
|
||||
let to = main_app_path.join(FRAMEWORKS_PATH).join(FRAMEWORK);
|
||||
if to.exists() {
|
||||
fs::remove_dir_all(&to).unwrap();
|
||||
}
|
||||
copy_directory(&cef_path.join(FRAMEWORK), &to)?;
|
||||
for helper in HELPERS {
|
||||
let helper = format!("{executable_name} {helper}");
|
||||
create_app(
|
||||
&main_app_path.join(FRAMEWORKS_PATH),
|
||||
&helper,
|
||||
true,
|
||||
None,
|
||||
bundle_info.clone(),
|
||||
&target_path.join(helper_name),
|
||||
)?;
|
||||
}
|
||||
if let Some(resources_path) = resources_path {
|
||||
let resources_path = resources_path.join("mac");
|
||||
let target_path = main_app_path.join(RESOURCES_PATH);
|
||||
copy_app_resources(&resources_path, &target_path)?;
|
||||
}
|
||||
Ok(main_app_path)
|
||||
}
|
||||
|
||||
/// Similar to [`bundle`], but this will invoke `cargo build` to build both the main executable and
|
||||
/// helper executable targets.
|
||||
pub fn build_bundle(
|
||||
app_path: &Path,
|
||||
executable_name: &str,
|
||||
bundle_info: BundleInfo,
|
||||
) -> Result<PathBuf> {
|
||||
let cargo_metadata = super::metadata::get_cargo_metadata()?;
|
||||
let target_path = cargo_metadata.target_directory().join("debug");
|
||||
let bundle_metadata = cargo_metadata.parse_bundle_metadata(executable_name)?;
|
||||
|
||||
cargo_build(executable_name)?;
|
||||
cargo_build(&bundle_metadata.helper_name)?;
|
||||
|
||||
bundle(
|
||||
app_path,
|
||||
&target_path,
|
||||
executable_name,
|
||||
&bundle_metadata.helper_name,
|
||||
bundle_metadata.resources_path,
|
||||
bundle_info,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct InfoPlist {
|
||||
#[serde(flatten)]
|
||||
bundle_info: BundleInfo,
|
||||
|
||||
#[serde(rename = "CFBundleExecutable")]
|
||||
executable_name: String,
|
||||
#[serde(rename = "CFBundleInfoDictionaryVersion")]
|
||||
bundle_info_dictionary_version: String,
|
||||
#[serde(rename = "CFBundlePackageType")]
|
||||
bundle_package_type: String,
|
||||
#[serde(rename = "CFBundleIconFile", skip_serializing_if = "String::is_empty")]
|
||||
icon_file: String,
|
||||
#[serde(rename = "CFBundleSignature")]
|
||||
bundle_signature: String,
|
||||
#[serde(
|
||||
rename = "CFBundleShortVersionString",
|
||||
serialize_with = "serialize_version"
|
||||
)]
|
||||
short_version: Version,
|
||||
#[serde(rename = "LSEnvironment")]
|
||||
environment: HashMap<String, String>,
|
||||
#[serde(rename = "LSFileQuarantineEnabled")]
|
||||
file_quarantine_enabled: bool,
|
||||
#[serde(rename = "LSMinimumSystemVersion")]
|
||||
minimum_system_version: String,
|
||||
#[serde(
|
||||
rename = "LSUIElement",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
serialize_with = "serialize_ui_element"
|
||||
)]
|
||||
ui_element: Option<&'static str>,
|
||||
#[serde(rename = "NSBluetoothAlwaysUsageDescription")]
|
||||
bluetooth_always_usage_description: String,
|
||||
#[serde(rename = "NSSupportsAutomaticGraphicsSwitching")]
|
||||
supports_automatic_graphics_switching: bool,
|
||||
#[serde(rename = "NSWebBrowserPublicKeyCredentialUsageDescription")]
|
||||
web_browser_publickey_credential_usage_description: String,
|
||||
#[serde(rename = "NSCameraUsageDescription")]
|
||||
camera_usage_description: String,
|
||||
#[serde(rename = "NSMicrophoneUsageDescription")]
|
||||
microphone_usage_description: String,
|
||||
}
|
||||
|
||||
impl InfoPlist {
|
||||
fn new(
|
||||
executable_name: &str,
|
||||
is_helper: bool,
|
||||
icon_file: Option<String>,
|
||||
bundle_info: BundleInfo,
|
||||
) -> Self {
|
||||
Self {
|
||||
executable_name: executable_name.to_owned(),
|
||||
bundle_info_dictionary_version: "6.0".to_owned(),
|
||||
bundle_package_type: "APPL".to_owned(),
|
||||
icon_file: icon_file.unwrap_or_default(),
|
||||
bundle_signature: "????".to_owned(),
|
||||
short_version: Version::new(
|
||||
bundle_info.version.major,
|
||||
bundle_info.version.minor,
|
||||
bundle_info.version.patch,
|
||||
),
|
||||
environment: [("MallocNanoZone".to_owned(), "0".to_owned())]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
file_quarantine_enabled: true,
|
||||
minimum_system_version: "11.0".to_owned(),
|
||||
ui_element: if is_helper { Some("1") } else { None },
|
||||
bluetooth_always_usage_description: executable_name.to_owned(),
|
||||
supports_automatic_graphics_switching: true,
|
||||
web_browser_publickey_credential_usage_description: executable_name.to_owned(),
|
||||
camera_usage_description: executable_name.to_owned(),
|
||||
microphone_usage_description: executable_name.to_owned(),
|
||||
bundle_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_ui_element<S>(
|
||||
ui_element: &Option<&'static str>,
|
||||
serializer: S,
|
||||
) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match ui_element {
|
||||
Some(element) => serializer.serialize_str(element),
|
||||
None => unreachable!("None is skipped"),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_version<S>(version: &Version, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&version.to_string())
|
||||
}
|
||||
|
||||
const EXEC_PATH: &str = "Contents/MacOS";
|
||||
const FRAMEWORKS_PATH: &str = "Contents/Frameworks";
|
||||
const RESOURCES_PATH: &str = "Contents/Resources";
|
||||
const FRAMEWORK: &str = "Chromium Embedded Framework.framework";
|
||||
const HELPERS: &[&str] = &[
|
||||
"Helper (GPU)",
|
||||
"Helper (Renderer)",
|
||||
"Helper (Plugin)",
|
||||
"Helper (Alerts)",
|
||||
"Helper",
|
||||
];
|
||||
|
||||
fn create_app_layout(app_path: &Path) -> Result<PathBuf> {
|
||||
for path in [EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH] {
|
||||
fs::create_dir_all(app_path.join(path))?;
|
||||
}
|
||||
Ok(app_path.join("Contents"))
|
||||
}
|
||||
|
||||
fn create_app(
|
||||
app_path: &Path,
|
||||
executable_name: &str,
|
||||
is_helper: bool,
|
||||
resources_path: Option<&Path>,
|
||||
bundle_info: BundleInfo,
|
||||
bin: &Path,
|
||||
) -> Result<PathBuf> {
|
||||
let app_path = app_path.join(executable_name).with_extension("app");
|
||||
let contents_path = create_app_layout(&app_path)?;
|
||||
let icon_file = resources_path.and_then(|path| {
|
||||
let icon_file = format!("{executable_name}.icns");
|
||||
if path.join("mac").join(&icon_file).exists() {
|
||||
Some(icon_file)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
create_info_plist(
|
||||
&contents_path,
|
||||
executable_name,
|
||||
bundle_info,
|
||||
is_helper,
|
||||
icon_file,
|
||||
)?;
|
||||
let executable_path = app_path.join(EXEC_PATH).join(executable_name);
|
||||
fs::copy(bin, executable_path)?;
|
||||
Ok(app_path)
|
||||
}
|
||||
|
||||
fn create_info_plist(
|
||||
contents_path: &Path,
|
||||
executable_name: &str,
|
||||
bundle_info: BundleInfo,
|
||||
is_helper: bool,
|
||||
icon_file: Option<String>,
|
||||
) -> Result<()> {
|
||||
let info_plist = InfoPlist::new(executable_name, is_helper, icon_file, bundle_info);
|
||||
plist::to_file_xml(contents_path.join("Info.plist"), &info_plist)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_directory(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
fs::create_dir_all(dst)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let dst_path = dst.join(entry.file_name());
|
||||
if entry.file_type()?.is_dir() {
|
||||
copy_directory(&entry.path(), &dst_path)?;
|
||||
} else {
|
||||
fs::copy(entry.path(), &dst_path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_app_resources(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
fs::create_dir_all(dst)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let mut dst_path = dst.join(entry.file_name());
|
||||
if entry.file_type()?.is_dir() {
|
||||
copy_app_resources(&entry.path(), &dst_path)?;
|
||||
} else {
|
||||
let entry = entry.path();
|
||||
if entry
|
||||
.extension()
|
||||
.map(|ext| ext == "xib")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
dst_path.set_extension("nib");
|
||||
let (Some(dst_path), Some(entry)) = (dst_path.to_str(), entry.to_str()) else {
|
||||
return Err(io::Error::from(io::ErrorKind::NotFound));
|
||||
};
|
||||
let status = Command::new("xcrun")
|
||||
.args(["ibtool", "--compile", dst_path, entry])
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted));
|
||||
}
|
||||
} else {
|
||||
fs::copy(entry, &dst_path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cargo_build(name: &str) -> Result<()> {
|
||||
println!("Building {name}...");
|
||||
|
||||
let status = Command::new(super::cargo_path())
|
||||
.args(["build", "--bin", name])
|
||||
.status()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::Interrupted).into())
|
||||
}
|
||||
}
|
||||
86
cef/src/build_util/metadata.rs
Normal file
86
cef/src/build_util/metadata.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Cargo metadata error: {0:?}")]
|
||||
Metadata(#[from] cargo_metadata::Error),
|
||||
#[error("Missing package metadata for {0}")]
|
||||
MissingPackageMetadata(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PackageMetadata {
|
||||
cef: Cef,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Cef {
|
||||
bundle: CargoBundleMetadata,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoBundleMetadata {
|
||||
#[cfg(target_os = "macos")]
|
||||
helper_name: String,
|
||||
resources_path: Option<String>,
|
||||
}
|
||||
|
||||
pub struct BundleMetadata {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub helper_name: String,
|
||||
pub resources_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl BundleMetadata {
|
||||
pub fn parse(executable: &str, metadata: &cargo_metadata::Metadata) -> Option<Self> {
|
||||
let package = metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|p| p.targets.iter().any(|t| t.name == executable))?;
|
||||
let package_metadata =
|
||||
serde_json::from_value::<PackageMetadata>(package.metadata.clone()).ok()?;
|
||||
let resources_path = package_metadata
|
||||
.cef
|
||||
.bundle
|
||||
.resources_path
|
||||
.as_deref()
|
||||
.and_then(|resources_path| {
|
||||
package
|
||||
.manifest_path
|
||||
.clone()
|
||||
.into_std_path_buf()
|
||||
.parent()
|
||||
.map(|manifest_dir| manifest_dir.join(resources_path))
|
||||
});
|
||||
Some(Self {
|
||||
#[cfg(target_os = "macos")]
|
||||
helper_name: package_metadata.cef.bundle.helper_name,
|
||||
resources_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CargoMetadata(cargo_metadata::Metadata);
|
||||
|
||||
impl CargoMetadata {
|
||||
pub fn target_directory(&self) -> PathBuf {
|
||||
PathBuf::from(&self.0.target_directory)
|
||||
}
|
||||
|
||||
pub fn parse_bundle_metadata(&self, executable: &str) -> Result<BundleMetadata> {
|
||||
BundleMetadata::parse(executable, &self.0)
|
||||
.ok_or_else(|| Error::MissingPackageMetadata(executable.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run `cargo metadata` to determine the configuration for the current workspace/package.
|
||||
pub fn get_cargo_metadata() -> Result<CargoMetadata> {
|
||||
let metadata = cargo_metadata::MetadataCommand::new()
|
||||
.no_deps()
|
||||
.other_options(vec!["--frozen".to_string()])
|
||||
.exec()?;
|
||||
Ok(CargoMetadata(metadata))
|
||||
}
|
||||
18
cef/src/build_util/mod.rs
Normal file
18
cef/src/build_util/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::env;
|
||||
|
||||
pub mod metadata;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod mac;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win;
|
||||
|
||||
/// Prefer the path in the `CARGO` environment variable if specified, otherwise just execute
|
||||
/// `cargo` from wherever it is found in the `PATH`.
|
||||
fn cargo_path() -> String {
|
||||
env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
|
||||
}
|
||||
117
cef/src/build_util/win/mod.rs
Normal file
117
cef/src/build_util/win/mod.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("I/O error: {0:?}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Metadata error: {0:?}")]
|
||||
Metadata(#[from] super::metadata::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-linux
|
||||
pub fn bundle(app_path: &Path, target_path: &Path, executable_name: &str) -> Result<PathBuf> {
|
||||
let cef_path = cef_dll_sys::get_cef_dir().unwrap();
|
||||
copy_directory(&cef_path, &app_path)?;
|
||||
|
||||
const LOCALES_DIR: &str = "locales";
|
||||
copy_directory(&cef_path.join(LOCALES_DIR), &app_path.join(LOCALES_DIR))?;
|
||||
|
||||
copy_app(app_path, target_path, executable_name)
|
||||
}
|
||||
|
||||
/// Similar to [`bundle`], but this will invoke `cargo build` to build the executable target.
|
||||
pub fn build_bundle(app_path: &Path, executable_name: &str, release: bool) -> Result<PathBuf> {
|
||||
let cargo_metadata = super::metadata::get_cargo_metadata()?;
|
||||
let target_path =
|
||||
cargo_metadata
|
||||
.target_directory()
|
||||
.join(if release { "release" } else { "debug" });
|
||||
|
||||
cargo_build(executable_name, release)?;
|
||||
|
||||
bundle(app_path, &target_path, executable_name)
|
||||
}
|
||||
|
||||
const MANIFEST_CONTENT: &[u8] = include_bytes!("cef-app.exe.manifest");
|
||||
|
||||
fn copy_app(app_path: &Path, target_path: &Path, executable_name: &str) -> Result<PathBuf> {
|
||||
let mut manifest_file =
|
||||
fs::File::create(app_path.join(format!("{executable_name}.exe.manifest")))?;
|
||||
manifest_file.write_all(MANIFEST_CONTENT)?;
|
||||
|
||||
#[cfg(feature = "sandbox")]
|
||||
{
|
||||
let dll_name = format!("{executable_name}.dll");
|
||||
let dll_path = app_path.join(&dll_name);
|
||||
let target_dll = target_path.join(&dll_name);
|
||||
fs::copy(&target_dll, &dll_path)?;
|
||||
|
||||
let pdb_name = format!("{executable_name}.pdb");
|
||||
let pdb_path = app_path.join(&pdb_name);
|
||||
let target_pdb = target_path.join(&pdb_name);
|
||||
fs::copy(&target_pdb, &pdb_path)?;
|
||||
|
||||
let executable_name = format!("{executable_name}.exe");
|
||||
let executable_path = app_path.join(&executable_name);
|
||||
let cef_path = cef_dll_sys::get_cef_dir().unwrap();
|
||||
let target_executable = cef_path.join("bootstrap.exe");
|
||||
fs::copy(&target_executable, &executable_path)?;
|
||||
|
||||
Ok(executable_path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sandbox"))]
|
||||
{
|
||||
let executable_name = format!("{executable_name}.exe");
|
||||
let executable_path = app_path.join(&executable_name);
|
||||
let target_executable = target_path.join(executable_name);
|
||||
fs::copy(&target_executable, &executable_path)?;
|
||||
|
||||
Ok(executable_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_directory(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
fs::create_dir_all(dst)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let dst_path = dst.join(entry.file_name());
|
||||
if entry.file_type()?.is_file()
|
||||
&& !entry
|
||||
.path()
|
||||
.extension()
|
||||
.map(|ext| ext == "exe")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
fs::copy(entry.path(), &dst_path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cargo_build(name: &str, release: bool) -> Result<()> {
|
||||
println!("Building {name}...");
|
||||
|
||||
let mut args = vec!["build"];
|
||||
if release {
|
||||
args.push("--release");
|
||||
}
|
||||
#[cfg(feature = "sandbox")]
|
||||
args.extend_from_slice(&["-p", name, "--lib"]);
|
||||
#[cfg(not(feature = "sandbox"))]
|
||||
args.extend_from_slice(&["--no-default-features", "--bin", name]);
|
||||
|
||||
let status = Command::new(super::cargo_path()).args(args).status()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::Interrupted).into())
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
pub mod args;
|
||||
pub mod rc;
|
||||
pub mod string;
|
||||
pub mod window_info;
|
||||
pub mod wrapper;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod application_mac;
|
||||
@@ -16,10 +18,15 @@ pub mod sandbox;
|
||||
#[cfg(feature = "accelerated_osr")]
|
||||
pub mod osr_texture_import;
|
||||
|
||||
#[cfg(feature = "build-util")]
|
||||
pub mod build_util;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod bindings;
|
||||
pub use bindings::*;
|
||||
|
||||
pub use rc::Rc as _;
|
||||
|
||||
pub use cef_dll_sys as sys;
|
||||
|
||||
#[cfg(all(
|
||||
@@ -27,3 +34,7 @@ pub use cef_dll_sys as sys;
|
||||
feature = "accelerated_osr"
|
||||
))]
|
||||
compile_error!("accelerated_osr not supported on this platform");
|
||||
|
||||
pub const SEEK_SET: i32 = 0;
|
||||
pub const SEEK_CUR: i32 = 1;
|
||||
pub const SEEK_END: i32 = 2;
|
||||
|
||||
@@ -43,6 +43,12 @@ impl Sandbox {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Sandbox {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sandbox {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
|
||||
@@ -856,6 +856,7 @@ impl<'a, T> From<&'a mut CefStringCollection<T>> for Option<&'a mut T> {
|
||||
}
|
||||
|
||||
/// See [_cef_string_list_t] for more documentation.
|
||||
#[derive(Clone)]
|
||||
pub struct CefStringList(CefStringCollection<_cef_string_list_t>);
|
||||
|
||||
impl CefStringList {
|
||||
@@ -988,6 +989,7 @@ impl Debug for CefStringList {
|
||||
}
|
||||
|
||||
/// See [_cef_string_map_t] for more documentation.
|
||||
#[derive(Clone)]
|
||||
pub struct CefStringMap(CefStringCollection<_cef_string_map_t>);
|
||||
|
||||
impl CefStringMap {
|
||||
@@ -1134,6 +1136,7 @@ impl Debug for CefStringMap {
|
||||
}
|
||||
|
||||
/// See [_cef_string_multimap_t] for more documentation.
|
||||
#[derive(Clone)]
|
||||
pub struct CefStringMultimap(CefStringCollection<_cef_string_multimap_t>);
|
||||
|
||||
impl CefStringMultimap {
|
||||
|
||||
60
cef/src/window_info.rs
Normal file
60
cef/src/window_info.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::{sys::cef_window_handle_t, *};
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::*;
|
||||
|
||||
impl WindowInfo {
|
||||
/// Create the browser as a child window.
|
||||
pub fn set_as_child(self, parent: cef_window_handle_t, bounds: &Rect) -> Self {
|
||||
Self {
|
||||
#[cfg(target_os = "windows")]
|
||||
style: WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_TABSTOP | WS_VISIBLE,
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
parent_window: parent,
|
||||
#[cfg(target_os = "macos")]
|
||||
parent_view: parent,
|
||||
bounds: bounds.clone(),
|
||||
#[cfg(target_os = "macos")]
|
||||
hidden: 0,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the browser as a popup window.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn set_as_popup(self, parent: cef_window_handle_t, title: &str) -> Self {
|
||||
Self {
|
||||
window_name: CefString::from(title),
|
||||
parent_window: parent,
|
||||
style: WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
|
||||
bounds: Rect {
|
||||
x: CW_USEDEFAULT,
|
||||
y: CW_USEDEFAULT,
|
||||
width: CW_USEDEFAULT,
|
||||
height: CW_USEDEFAULT,
|
||||
},
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the browser using windowless (off-screen) rendering. No window
|
||||
/// will be created for the browser and all rendering will occur via the
|
||||
/// CefRenderHandler interface. The |parent| value will be used to identify
|
||||
/// monitor info and to act as the parent window for dialogs, context menus,
|
||||
/// etc. If |parent| is not provided then the main screen monitor will be used
|
||||
/// and some functionality that requires a parent window may not function
|
||||
/// correctly. In order to create windowless browsers the
|
||||
/// CefSettings.windowless_rendering_enabled value must be set to true.
|
||||
/// Transparent painting is enabled by default but can be disabled by setting
|
||||
/// CefBrowserSettings.background_color to an opaque value.
|
||||
pub fn set_as_windowless(self, parent: cef_window_handle_t) -> Self {
|
||||
Self {
|
||||
windowless_rendering_enabled: 1,
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
parent_window: parent,
|
||||
#[cfg(target_os = "macos")]
|
||||
parent_view: parent,
|
||||
runtime_style: RuntimeStyle::ALLOY,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
164
cef/src/wrapper/browser_info_map.rs
Normal file
164
cef/src/wrapper/browser_info_map.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::{collections::BTreeMap, ops::ControlFlow};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BrowserInfoMapVisitorResult {
|
||||
/// Remove the entry from the map.
|
||||
RemoveEntry,
|
||||
/// Keep the entry in the map.
|
||||
KeepEntry,
|
||||
}
|
||||
|
||||
/// Implement this interface to visit and optionally delete objects in the map.
|
||||
pub trait BrowserInfoMapVisitor<K: Copy + Ord, V: Clone> {
|
||||
fn on_next_info(
|
||||
&self,
|
||||
browser_id: i32,
|
||||
key: K,
|
||||
value: &V,
|
||||
) -> ControlFlow<BrowserInfoMapVisitorResult, BrowserInfoMapVisitorResult>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BrowserInfoMap<K: Clone + Ord, V: Clone> {
|
||||
map: BTreeMap<i32, BTreeMap<K, V>>,
|
||||
}
|
||||
|
||||
impl<K: Copy + Ord, V: Clone> BrowserInfoMap<K, V> {
|
||||
/// Add an object associated with the specified ID values.
|
||||
pub fn insert(&mut self, browser_id: i32, key: K, value: V) {
|
||||
self.map.entry(browser_id).or_default().insert(key, value);
|
||||
}
|
||||
|
||||
/// Find the object with the specified ID values. |visitor| can optionally be
|
||||
/// used to evaluate or remove the object at the same time. If the object is
|
||||
/// removed using the Visitor the caller is responsible for destroying it.
|
||||
pub fn find(
|
||||
&mut self,
|
||||
browser_id: i32,
|
||||
key: K,
|
||||
visitor: Option<&dyn BrowserInfoMapVisitor<K, V>>,
|
||||
) -> Option<V> {
|
||||
let info_map = self.map.get_mut(&browser_id)?;
|
||||
let entry = info_map.get(&key)?;
|
||||
|
||||
if let Some(visitor) = visitor {
|
||||
let result = match visitor.on_next_info(browser_id, key, entry) {
|
||||
ControlFlow::Break(result) => result,
|
||||
ControlFlow::Continue(result) => result,
|
||||
};
|
||||
|
||||
if result == BrowserInfoMapVisitorResult::RemoveEntry {
|
||||
let entry = info_map.remove(&key);
|
||||
if info_map.is_empty() {
|
||||
self.map.remove(&browser_id);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
Some(entry.clone())
|
||||
}
|
||||
|
||||
/// Find all objects. If any objects are removed using the Visitor the caller
|
||||
/// is responsible for destroying them.
|
||||
pub fn find_all(&mut self, visitor: &dyn BrowserInfoMapVisitor<K, V>) {
|
||||
let browser_ids: Vec<_> = self.map.keys().copied().collect();
|
||||
for browser_id in browser_ids {
|
||||
let info_map = self
|
||||
.map
|
||||
.get_mut(&browser_id)
|
||||
.expect("missing browser info map");
|
||||
|
||||
let mut keep_going = true;
|
||||
let mut removed = vec![];
|
||||
let keys: Vec<_> = info_map.keys().copied().collect();
|
||||
for key in keys {
|
||||
let value = info_map.get(&key).expect("missing value");
|
||||
let result = visitor.on_next_info(browser_id, key, value);
|
||||
let (stop, result) = match result {
|
||||
ControlFlow::Break(result) => (true, result),
|
||||
ControlFlow::Continue(result) => (false, result),
|
||||
};
|
||||
|
||||
if result == BrowserInfoMapVisitorResult::RemoveEntry {
|
||||
removed.push(key);
|
||||
}
|
||||
|
||||
if stop {
|
||||
keep_going = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for key in removed {
|
||||
info_map.remove(&key);
|
||||
}
|
||||
|
||||
if info_map.is_empty() {
|
||||
self.map.remove(&browser_id);
|
||||
}
|
||||
|
||||
if !keep_going {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find all objects associated with the specified browser. If any objects are
|
||||
/// removed using the Visitor the caller is responsible for destroying them.
|
||||
pub fn find_browser_all(&mut self, browser_id: i32, visitor: &dyn BrowserInfoMapVisitor<K, V>) {
|
||||
let info_map = self
|
||||
.map
|
||||
.get_mut(&browser_id)
|
||||
.expect("missing browser info map");
|
||||
|
||||
let mut removed = vec![];
|
||||
let keys: Vec<_> = info_map.keys().copied().collect();
|
||||
for key in keys {
|
||||
let value = info_map.get(&key).expect("missing value");
|
||||
let result = visitor.on_next_info(browser_id, key, value);
|
||||
let (stop, result) = match result {
|
||||
ControlFlow::Break(result) => (true, result),
|
||||
ControlFlow::Continue(result) => (false, result),
|
||||
};
|
||||
|
||||
if result == BrowserInfoMapVisitorResult::RemoveEntry {
|
||||
removed.push(key);
|
||||
}
|
||||
|
||||
if stop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for key in removed {
|
||||
info_map.remove(&key);
|
||||
}
|
||||
|
||||
if info_map.is_empty() {
|
||||
self.map.remove(&browser_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the map is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the number of objects in the map.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.values().map(|info_map| info_map.len()).sum()
|
||||
}
|
||||
|
||||
/// Returns the number of objects in the map that are associated with the
|
||||
/// specified browser.
|
||||
pub fn browser_len(&self, browser_id: i32) -> usize {
|
||||
self.map
|
||||
.get(&browser_id)
|
||||
.map_or(0, |info_map| info_map.len())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
}
|
||||
}
|
||||
110
cef/src/wrapper/byte_read_handler.rs
Normal file
110
cef/src/wrapper/byte_read_handler.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
//! Thread safe implementation of the [ReadHandler] type for reading an in-memory array of bytes.
|
||||
use crate::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct ByteStream {
|
||||
bytes: Vec<u8>,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl ByteStream {
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
ByteStream { bytes, offset: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
wrap_read_handler! {
|
||||
pub struct ByteReadHandler {
|
||||
stream: Arc<Mutex<ByteStream>>,
|
||||
}
|
||||
|
||||
impl ReadHandler {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn read(&self, ptr: *mut u8, size: usize, n: usize) -> usize {
|
||||
let Ok(mut stream) = self.stream.lock() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let s = (stream.bytes.len() - stream.offset) / size;
|
||||
let ret = s.min(n);
|
||||
let buffer = unsafe { std::slice::from_raw_parts_mut(ptr, ret * size) };
|
||||
buffer.copy_from_slice(&stream.bytes[stream.offset..stream.offset + ret * size]);
|
||||
stream.offset += ret * size;
|
||||
ret
|
||||
}
|
||||
|
||||
fn seek(&self, offset: i64, whence: ::std::os::raw::c_int) -> ::std::os::raw::c_int {
|
||||
let Ok(mut stream) = self.stream.lock() else {
|
||||
return -1;
|
||||
};
|
||||
|
||||
const SEEK_SET: i32 = 0;
|
||||
const SEEK_CUR: i32 = 1;
|
||||
const SEEK_END: i32 = 2;
|
||||
|
||||
match whence {
|
||||
SEEK_SET => {
|
||||
if offset < 0 {
|
||||
return -1;
|
||||
}
|
||||
let offset = offset as usize;
|
||||
if offset > stream.bytes.len() {
|
||||
return -1;
|
||||
}
|
||||
stream.offset = offset;
|
||||
0
|
||||
}
|
||||
SEEK_CUR => {
|
||||
if offset < 0 {
|
||||
let offset = -offset as usize;
|
||||
if offset > stream.offset {
|
||||
return -1;
|
||||
}
|
||||
stream.offset -= offset;
|
||||
} else {
|
||||
let offset = offset as usize;
|
||||
if offset + stream.offset > stream.bytes.len() {
|
||||
return -1;
|
||||
}
|
||||
stream.offset += offset;
|
||||
}
|
||||
0
|
||||
}
|
||||
SEEK_END => {
|
||||
if offset > 0 {
|
||||
return -1;
|
||||
}
|
||||
let offset = -offset as usize;
|
||||
if offset > stream.bytes.len() {
|
||||
return -1;
|
||||
}
|
||||
stream.offset = stream.bytes.len() - offset;
|
||||
0
|
||||
}
|
||||
_ => -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn tell(&self) -> i64 {
|
||||
let Ok(stream) = self.stream.lock() else {
|
||||
return 0;
|
||||
};
|
||||
stream.offset as i64
|
||||
}
|
||||
|
||||
fn eof(&self) -> i32 {
|
||||
let Ok(stream) = self.stream.lock() else {
|
||||
return 1;
|
||||
};
|
||||
if stream.offset >= stream.bytes.len() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn may_block(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
1773
cef/src/wrapper/message_router.rs
Normal file
1773
cef/src/wrapper/message_router.rs
Normal file
File diff suppressed because it is too large
Load Diff
726
cef/src/wrapper/message_router_utils.rs
Normal file
726
cef/src/wrapper/message_router_utils.rs
Normal file
@@ -0,0 +1,726 @@
|
||||
use super::message_router::*;
|
||||
use crate::*;
|
||||
use std::{
|
||||
io::{self, Cursor, Read, Write},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
const NO_ERROR: i32 = 0;
|
||||
|
||||
const CONTEXT_ID: usize = 0;
|
||||
const REQUEST_ID: usize = 1;
|
||||
const RENDERER_PAYLOAD: usize = 2;
|
||||
const IS_SUCCESS: usize = 2;
|
||||
const BROWSER_PAYLOAD: usize = 3;
|
||||
const IS_PERSISTENT: usize = 3;
|
||||
|
||||
pub trait ProcessMessageBuilder {
|
||||
fn build_browser_response(&self, context_id: i32, request_id: i32) -> Option<ProcessMessage>;
|
||||
fn build_renderer_message(
|
||||
&self,
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
persistent: bool,
|
||||
) -> Option<ProcessMessage>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub enum MessagePayload {
|
||||
#[default]
|
||||
Empty,
|
||||
String(CefStringUtf8),
|
||||
Binary(Arc<dyn BinaryBuffer>),
|
||||
}
|
||||
|
||||
impl MessagePayload {
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::Empty => 0,
|
||||
Self::String(s) => s.as_slice().map(|s| s.len()).unwrap_or(0),
|
||||
Self::Binary(b) => b.data().len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_string<R: Read>(f: &mut R) -> io::Result<Self> {
|
||||
let mut buffer = vec![];
|
||||
f.read_to_end(&mut buffer)?;
|
||||
Ok(String::from_utf8(buffer)
|
||||
.map(|value| value.as_str().into())
|
||||
.unwrap_or(Self::Empty))
|
||||
}
|
||||
|
||||
// fn read_binary<R: Read>(f: &mut R) -> io::Result<Self> {
|
||||
// let mut buffer = vec![];
|
||||
// f.read_to_end(&mut buffer)?;
|
||||
// Ok(binary_value_create(Some(&buffer))
|
||||
// .map(|value| Self::Binary(Arc::new(BinaryValueBuffer::new(None, Some(value)))))
|
||||
// .unwrap_or(Self::Empty))
|
||||
// }
|
||||
|
||||
fn write<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
let buffer = match self {
|
||||
Self::String(s) => s.as_slice(),
|
||||
Self::Binary(b) if !b.data().is_empty() => Some(b.data()),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(buffer) = buffer {
|
||||
f.write_all(buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CefString> for MessagePayload {
|
||||
fn from(s: &CefString) -> Self {
|
||||
Self::String(CefStringUtf8::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for MessagePayload {
|
||||
fn from(s: &str) -> Self {
|
||||
Self::String(CefStringUtf8::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<&V8Value>> for MessagePayload {
|
||||
fn from(v: Option<&V8Value>) -> Self {
|
||||
let b = v.and_then(|v| {
|
||||
let ptr = v.array_buffer_data();
|
||||
let size = v.array_buffer_byte_length();
|
||||
if ptr.is_null() || size == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let data = unsafe { slice::from_raw_parts(ptr as *const u8, size) };
|
||||
binary_value_create(Some(data))
|
||||
});
|
||||
b.map(|value| Self::Binary(Arc::new(BinaryValueBuffer::new(None, Some(value)))))
|
||||
.unwrap_or(Self::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for MessagePayload {
|
||||
fn from(data: &[u8]) -> Self {
|
||||
if data.is_empty() {
|
||||
Self::Empty
|
||||
} else {
|
||||
Self::Binary(Arc::new(BinaryValueBuffer::new(
|
||||
None,
|
||||
binary_value_create(Some(data)),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BrowserMessage {
|
||||
pub context_id: i32,
|
||||
pub request_id: i32,
|
||||
pub is_success: bool,
|
||||
pub error_code: i32,
|
||||
pub payload: MessagePayload,
|
||||
}
|
||||
|
||||
impl From<Option<ProcessMessage>> for BrowserMessage {
|
||||
fn from(message: Option<ProcessMessage>) -> Self {
|
||||
let Some(message) = message else {
|
||||
return Default::default();
|
||||
};
|
||||
|
||||
if let Some(args) = message.argument_list() {
|
||||
let context_id = args.int(CONTEXT_ID);
|
||||
let request_id = args.int(REQUEST_ID);
|
||||
let is_success = args.bool(IS_SUCCESS);
|
||||
|
||||
if is_success != 0 {
|
||||
debug_assert_eq!(args.size(), 4);
|
||||
match args.get_type(BROWSER_PAYLOAD) {
|
||||
ValueType::STRING => Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_success: true,
|
||||
error_code: NO_ERROR,
|
||||
payload: MessagePayload::String(CefStringUtf8::from(&CefString::from(
|
||||
&args.string(BROWSER_PAYLOAD),
|
||||
))),
|
||||
},
|
||||
ValueType::BINARY => Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_success: true,
|
||||
error_code: NO_ERROR,
|
||||
payload: MessagePayload::Binary(Arc::new(BinaryValueBuffer::new(
|
||||
Some(message),
|
||||
args.binary(BROWSER_PAYLOAD),
|
||||
))),
|
||||
},
|
||||
payload_type => {
|
||||
assert_eq!(payload_type, ValueType::NULL);
|
||||
Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_success: true,
|
||||
error_code: NO_ERROR,
|
||||
payload: MessagePayload::Binary(Arc::new(EmptyBinaryBuffer)),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug_assert_eq!(args.size(), 5);
|
||||
Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_success: false,
|
||||
error_code: args.int(3),
|
||||
payload: MessagePayload::String(CefStringUtf8::from(&CefString::from(
|
||||
&args.string(4),
|
||||
))),
|
||||
}
|
||||
}
|
||||
} else if let Some(region) = message
|
||||
.shared_memory_region()
|
||||
.filter(|region| region.is_valid() != 0)
|
||||
{
|
||||
debug_assert!(region.size() >= BrowserMessageHeader::SIZE);
|
||||
debug_assert!(!region.memory().is_null());
|
||||
let buffer =
|
||||
unsafe { slice::from_raw_parts(region.memory() as *const u8, region.size()) };
|
||||
let header = {
|
||||
let mut cursor = Cursor::new(&buffer[..BrowserMessageHeader::SIZE]);
|
||||
BrowserMessageHeader::read(&mut cursor).unwrap_or_default()
|
||||
};
|
||||
if header.is_binary {
|
||||
Self {
|
||||
context_id: header.context_id,
|
||||
request_id: header.request_id,
|
||||
is_success: true,
|
||||
error_code: NO_ERROR,
|
||||
payload: MessagePayload::Binary(Arc::new(SharedMemoryRegionBuffer::new(
|
||||
Some(region),
|
||||
BrowserMessageHeader::SIZE,
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
let mut cursor = Cursor::new(&buffer[BrowserMessageHeader::SIZE..]);
|
||||
Self {
|
||||
context_id: header.context_id,
|
||||
request_id: header.request_id,
|
||||
is_success: true,
|
||||
error_code: NO_ERROR,
|
||||
payload: MessagePayload::read_string(&mut cursor).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct RenderMessage {
|
||||
pub context_id: i32,
|
||||
pub request_id: i32,
|
||||
pub is_persistent: bool,
|
||||
pub payload: MessagePayload,
|
||||
}
|
||||
|
||||
impl From<Option<ProcessMessage>> for RenderMessage {
|
||||
fn from(message: Option<ProcessMessage>) -> Self {
|
||||
let Some(message) = message else {
|
||||
return Default::default();
|
||||
};
|
||||
|
||||
if let Some(args) = message.argument_list() {
|
||||
debug_assert_eq!(args.size(), 4);
|
||||
let context_id = args.int(CONTEXT_ID);
|
||||
let request_id = args.int(REQUEST_ID);
|
||||
let is_persistent = args.bool(IS_PERSISTENT) != 0;
|
||||
|
||||
match args.get_type(RENDERER_PAYLOAD) {
|
||||
ValueType::STRING => Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_persistent,
|
||||
payload: MessagePayload::String(CefStringUtf8::from(&CefString::from(
|
||||
&args.string(RENDERER_PAYLOAD),
|
||||
))),
|
||||
},
|
||||
ValueType::BINARY => Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_persistent,
|
||||
payload: MessagePayload::Binary(Arc::new(BinaryValueBuffer::new(
|
||||
Some(message),
|
||||
args.binary(RENDERER_PAYLOAD),
|
||||
))),
|
||||
},
|
||||
payload_type => {
|
||||
debug_assert_eq!(payload_type, ValueType::NULL);
|
||||
Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_persistent,
|
||||
payload: MessagePayload::Binary(Arc::new(EmptyBinaryBuffer)),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(region) = message
|
||||
.shared_memory_region()
|
||||
.filter(|region| region.is_valid() != 0)
|
||||
{
|
||||
debug_assert!(region.size() >= RendererMessageHeader::<true>::SIZE);
|
||||
debug_assert!(!region.memory().is_null());
|
||||
let buffer =
|
||||
unsafe { slice::from_raw_parts(region.memory() as *const u8, region.size()) };
|
||||
let header = {
|
||||
let mut cursor = Cursor::new(&buffer[..RendererMessageHeader::<true>::SIZE]);
|
||||
RendererMessageHeader::<true>::read(&mut cursor).unwrap_or_default()
|
||||
};
|
||||
if header.is_binary {
|
||||
Self {
|
||||
context_id: header.context_id,
|
||||
request_id: header.request_id,
|
||||
is_persistent: header.is_persistent,
|
||||
payload: MessagePayload::Binary(Arc::new(SharedMemoryRegionBuffer::new(
|
||||
Some(region),
|
||||
RendererMessageHeader::<true>::SIZE,
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
let mut cursor = Cursor::new(&buffer[RendererMessageHeader::<true>::SIZE..]);
|
||||
Self {
|
||||
context_id: header.context_id,
|
||||
request_id: header.request_id,
|
||||
is_persistent: header.is_persistent,
|
||||
payload: MessagePayload::read_string(&mut cursor).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sandbox"))]
|
||||
wrap_v8_array_buffer_release_callback! {
|
||||
pub struct BinaryValueArrayBufferReleaseCallback {
|
||||
value: MessagePayload,
|
||||
}
|
||||
|
||||
impl V8ArrayBufferReleaseCallback {
|
||||
fn release_buffer(&self, _buffer: *mut u8) {}
|
||||
}
|
||||
}
|
||||
|
||||
trait MessageHeader: Sized {
|
||||
const SIZE: usize;
|
||||
|
||||
fn new(context_id: i32, request_id: i32, is_binary: bool) -> Self;
|
||||
fn read<R: Read>(f: &mut R) -> io::Result<Self>;
|
||||
fn write<W: Write>(&self, f: &mut W) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct BrowserMessageHeader {
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
is_binary: bool,
|
||||
}
|
||||
|
||||
impl MessageHeader for BrowserMessageHeader {
|
||||
const SIZE: usize = mem::size_of::<i32>() * 2 + 1;
|
||||
|
||||
fn new(context_id: i32, request_id: i32, is_binary: bool) -> Self {
|
||||
Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_binary,
|
||||
}
|
||||
}
|
||||
|
||||
fn read<R: Read>(f: &mut R) -> io::Result<Self> {
|
||||
let mut data = [0_u8; mem::size_of::<i32>()];
|
||||
f.read_exact(&mut data)?;
|
||||
let context_id = i32::from_ne_bytes(data);
|
||||
f.read_exact(&mut data)?;
|
||||
let request_id = i32::from_ne_bytes(data);
|
||||
let mut flags = [0_u8];
|
||||
f.read_exact(&mut flags)?;
|
||||
let is_binary = flags[0] != 0;
|
||||
|
||||
Ok(Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_binary,
|
||||
})
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
f.write_all(&self.context_id.to_ne_bytes())?;
|
||||
f.write_all(&self.request_id.to_ne_bytes())?;
|
||||
f.write_all(&[self.is_binary.into()])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct RendererMessageHeader<const DEFAULT_PERSISTENT: bool> {
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
is_persistent: bool,
|
||||
is_binary: bool,
|
||||
}
|
||||
|
||||
impl<const DEFAULT_PERSISTENT: bool> MessageHeader for RendererMessageHeader<DEFAULT_PERSISTENT> {
|
||||
const SIZE: usize = mem::size_of::<i32>() * 2 + 2;
|
||||
|
||||
fn new(context_id: i32, request_id: i32, is_binary: bool) -> Self {
|
||||
Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_persistent: DEFAULT_PERSISTENT,
|
||||
is_binary,
|
||||
}
|
||||
}
|
||||
|
||||
fn read<R: Read>(f: &mut R) -> io::Result<Self> {
|
||||
let mut data = [0_u8; mem::size_of::<i32>()];
|
||||
f.read_exact(&mut data)?;
|
||||
let context_id = i32::from_ne_bytes(data);
|
||||
f.read_exact(&mut data)?;
|
||||
let request_id = i32::from_ne_bytes(data);
|
||||
let mut flags = [0_u8; 2];
|
||||
f.read_exact(&mut flags)?;
|
||||
let is_persistent = flags[0] != 0;
|
||||
let is_binary = flags[1] != 0;
|
||||
|
||||
Ok(Self {
|
||||
context_id,
|
||||
request_id,
|
||||
is_persistent,
|
||||
is_binary,
|
||||
})
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
f.write_all(&self.context_id.to_ne_bytes())?;
|
||||
f.write_all(&self.request_id.to_ne_bytes())?;
|
||||
f.write_all(&[self.is_persistent.into()])?;
|
||||
f.write_all(&[self.is_binary.into()])
|
||||
}
|
||||
}
|
||||
|
||||
fn build_browser_list_message(
|
||||
name: &str,
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
payload: MessagePayload,
|
||||
) -> Option<ProcessMessage> {
|
||||
let message = process_message_create(Some(&CefString::from(name)))?;
|
||||
let args = message.argument_list()?;
|
||||
args.set_int(CONTEXT_ID, context_id);
|
||||
args.set_int(REQUEST_ID, request_id);
|
||||
args.set_bool(IS_SUCCESS, 1);
|
||||
|
||||
match payload {
|
||||
MessagePayload::Empty => args.set_null(BROWSER_PAYLOAD),
|
||||
MessagePayload::String(value) => {
|
||||
args.set_string(BROWSER_PAYLOAD, Some(&CefString::from(&value)))
|
||||
}
|
||||
MessagePayload::Binary(value) => args.set_binary(
|
||||
BROWSER_PAYLOAD,
|
||||
binary_value_create(Some(value.data())).as_mut(),
|
||||
),
|
||||
};
|
||||
|
||||
Some(message)
|
||||
}
|
||||
|
||||
fn build_render_list_message(
|
||||
name: &str,
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
payload: MessagePayload,
|
||||
is_persistent: bool,
|
||||
) -> Option<ProcessMessage> {
|
||||
let message = process_message_create(Some(&CefString::from(name)))?;
|
||||
let args = message.argument_list()?;
|
||||
args.set_int(CONTEXT_ID, context_id);
|
||||
args.set_int(REQUEST_ID, request_id);
|
||||
|
||||
match payload {
|
||||
MessagePayload::Empty => args.set_null(RENDERER_PAYLOAD),
|
||||
MessagePayload::String(value) => {
|
||||
args.set_string(RENDERER_PAYLOAD, Some(&CefString::from(&value)))
|
||||
}
|
||||
MessagePayload::Binary(value) => args.set_binary(
|
||||
RENDERER_PAYLOAD,
|
||||
binary_value_create(Some(value.data())).as_mut(),
|
||||
),
|
||||
};
|
||||
|
||||
args.set_bool(IS_PERSISTENT, is_persistent.into());
|
||||
|
||||
Some(message)
|
||||
}
|
||||
|
||||
struct MessagePayloadBuilder {
|
||||
pub name: String,
|
||||
pub payload: MessagePayload,
|
||||
}
|
||||
|
||||
impl ProcessMessageBuilder for MessagePayloadBuilder {
|
||||
fn build_browser_response(&self, context_id: i32, request_id: i32) -> Option<ProcessMessage> {
|
||||
build_browser_list_message(&self.name, context_id, request_id, self.payload.clone())
|
||||
}
|
||||
|
||||
fn build_renderer_message(
|
||||
&self,
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
persistent: bool,
|
||||
) -> Option<ProcessMessage> {
|
||||
build_render_list_message(
|
||||
&self.name,
|
||||
context_id,
|
||||
request_id,
|
||||
self.payload.clone(),
|
||||
persistent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum SharedProcessMessageRouter<Header>
|
||||
where
|
||||
Header: MessageHeader,
|
||||
{
|
||||
SharedMemory {
|
||||
builder: SharedProcessMessageBuilder,
|
||||
is_binary: bool,
|
||||
_phantom: PhantomData<Header>,
|
||||
},
|
||||
Payload(MessagePayloadBuilder),
|
||||
}
|
||||
|
||||
impl<Header> SharedProcessMessageRouter<Header>
|
||||
where
|
||||
Header: MessageHeader,
|
||||
{
|
||||
fn new(name: &str, payload: MessagePayload) -> Self {
|
||||
let message_size = Header::SIZE + payload.size();
|
||||
let builder =
|
||||
match shared_process_message_builder_create(Some(&CefString::from(name)), message_size)
|
||||
{
|
||||
Some(builder) if builder.is_valid() != 0 => builder,
|
||||
_ => {
|
||||
return Self::Payload(MessagePayloadBuilder {
|
||||
name: name.to_owned(),
|
||||
payload,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let buffer =
|
||||
unsafe { slice::from_raw_parts_mut(builder.memory() as *mut u8, message_size) };
|
||||
let mut cursor = Cursor::new(&mut buffer[Header::SIZE..]);
|
||||
if payload.write(&mut cursor).is_err() {
|
||||
return Self::Payload(MessagePayloadBuilder {
|
||||
name: name.to_owned(),
|
||||
payload,
|
||||
});
|
||||
}
|
||||
|
||||
Self::SharedMemory {
|
||||
builder,
|
||||
is_binary: matches!(payload, MessagePayload::Binary(_)),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Header> ProcessMessageBuilder for SharedProcessMessageRouter<Header>
|
||||
where
|
||||
Header: MessageHeader,
|
||||
{
|
||||
fn build_browser_response(&self, context_id: i32, request_id: i32) -> Option<ProcessMessage> {
|
||||
match self {
|
||||
Self::SharedMemory {
|
||||
builder, is_binary, ..
|
||||
} => {
|
||||
let buffer =
|
||||
unsafe { slice::from_raw_parts_mut(builder.memory() as *mut u8, Header::SIZE) };
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
Header::new(context_id, request_id, *is_binary)
|
||||
.write(&mut cursor)
|
||||
.ok()?;
|
||||
|
||||
builder.build()
|
||||
}
|
||||
Self::Payload(builder) => builder.build_browser_response(context_id, request_id),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_renderer_message(
|
||||
&self,
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
persistent: bool,
|
||||
) -> Option<ProcessMessage> {
|
||||
match self {
|
||||
Self::SharedMemory {
|
||||
builder, is_binary, ..
|
||||
} => {
|
||||
let buffer =
|
||||
unsafe { slice::from_raw_parts_mut(builder.memory() as *mut u8, Header::SIZE) };
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
Header::new(context_id, request_id, *is_binary)
|
||||
.write(&mut cursor)
|
||||
.ok()?;
|
||||
|
||||
builder.build()
|
||||
}
|
||||
Self::Payload(builder) => {
|
||||
builder.build_renderer_message(context_id, request_id, persistent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptyBinaryBuffer;
|
||||
|
||||
impl BinaryBuffer for EmptyBinaryBuffer {
|
||||
fn data(&self) -> &[u8] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn data_mut(&mut self) -> &mut [u8] {
|
||||
&mut []
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BinaryValueBuffer {
|
||||
_message: Option<ProcessMessage>,
|
||||
value: Option<BinaryValue>,
|
||||
}
|
||||
|
||||
impl BinaryValueBuffer {
|
||||
pub fn new(message: Option<ProcessMessage>, value: Option<BinaryValue>) -> Self {
|
||||
Self {
|
||||
_message: message,
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryBuffer for BinaryValueBuffer {
|
||||
fn data(&self) -> &[u8] {
|
||||
self.value.as_ref().map_or(&[], |v| unsafe {
|
||||
slice::from_raw_parts(v.raw_data() as *const u8, v.size())
|
||||
})
|
||||
}
|
||||
|
||||
fn data_mut(&mut self) -> &mut [u8] {
|
||||
self.value.as_mut().map_or(&mut [], |v| unsafe {
|
||||
slice::from_raw_parts_mut(v.raw_data() as *mut u8, v.size())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SharedMemoryRegionBuffer {
|
||||
region: Option<SharedMemoryRegion>,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl SharedMemoryRegionBuffer {
|
||||
fn new(region: Option<SharedMemoryRegion>, offset: usize) -> Self {
|
||||
Self { region, offset }
|
||||
}
|
||||
|
||||
fn data(&self) -> *mut u8 {
|
||||
self.region
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null_mut(), |r| unsafe {
|
||||
r.memory().add(self.offset) as *mut u8
|
||||
})
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.region
|
||||
.as_ref()
|
||||
.map_or(0, |r| r.size().saturating_sub(self.offset))
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryBuffer for SharedMemoryRegionBuffer {
|
||||
fn data(&self) -> &[u8] {
|
||||
let data = self.data();
|
||||
let size = self.size();
|
||||
if data.is_null() || size == 0 {
|
||||
&[]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(data as *const u8, size) }
|
||||
}
|
||||
}
|
||||
|
||||
fn data_mut(&mut self) -> &mut [u8] {
|
||||
let data = self.data();
|
||||
let size = self.size();
|
||||
if data.is_null() || size == 0 {
|
||||
&mut []
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts_mut(data, size) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_browser_response_builder(
|
||||
threshold: usize,
|
||||
name: &str,
|
||||
payload: MessagePayload,
|
||||
) -> Rc<dyn ProcessMessageBuilder> {
|
||||
if payload.size() < threshold {
|
||||
Rc::new(MessagePayloadBuilder {
|
||||
name: name.to_string(),
|
||||
payload,
|
||||
})
|
||||
} else {
|
||||
Rc::new(SharedProcessMessageRouter::<BrowserMessageHeader>::new(
|
||||
name, payload,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_renderer_message(
|
||||
threshold: usize,
|
||||
name: &str,
|
||||
context_id: i32,
|
||||
request_id: i32,
|
||||
request: Option<&V8Value>,
|
||||
persistent: bool,
|
||||
) -> Option<ProcessMessage> {
|
||||
let payload = request
|
||||
.map(|request| {
|
||||
if request.is_string() != 0 {
|
||||
MessagePayload::from(&CefString::from(&request.string_value()))
|
||||
} else {
|
||||
MessagePayload::from(Some(request))
|
||||
}
|
||||
})
|
||||
.unwrap_or(MessagePayload::Empty);
|
||||
|
||||
let builder: Box<dyn ProcessMessageBuilder> = if payload.size() < threshold {
|
||||
Box::new(MessagePayloadBuilder {
|
||||
name: name.to_string(),
|
||||
payload,
|
||||
})
|
||||
} else if persistent {
|
||||
Box::new(SharedProcessMessageRouter::<RendererMessageHeader<true>>::new(name, payload))
|
||||
} else {
|
||||
Box::new(SharedProcessMessageRouter::<RendererMessageHeader<false>>::new(name, payload))
|
||||
};
|
||||
|
||||
builder.build_renderer_message(context_id, request_id, persistent)
|
||||
}
|
||||
8
cef/src/wrapper/mod.rs
Normal file
8
cef/src/wrapper/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
pub mod browser_info_map;
|
||||
pub mod byte_read_handler;
|
||||
pub mod message_router;
|
||||
pub mod resource_manager;
|
||||
pub mod stream_resource_handler;
|
||||
pub mod zip_archive;
|
||||
|
||||
mod message_router_utils;
|
||||
1155
cef/src/wrapper/resource_manager.rs
Normal file
1155
cef/src/wrapper/resource_manager.rs
Normal file
File diff suppressed because it is too large
Load Diff
121
cef/src/wrapper/stream_resource_handler.rs
Normal file
121
cef/src/wrapper/stream_resource_handler.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
//! Implementation of the ResourceHandler class for reading from a Stream.
|
||||
use crate::*;
|
||||
|
||||
wrap_resource_handler! {
|
||||
pub struct StreamResourceHandler {
|
||||
status_code: i32,
|
||||
status_text: String,
|
||||
mime_type: String,
|
||||
header_map: Option<CefStringMultimap>,
|
||||
stream: Option<StreamReader>,
|
||||
}
|
||||
|
||||
impl ResourceHandler {
|
||||
fn open(
|
||||
&self,
|
||||
_request: Option<&mut Request>,
|
||||
handle_request: Option<&mut i32>,
|
||||
_callback: Option<&mut Callback>,
|
||||
) -> i32 {
|
||||
debug_assert_eq!(
|
||||
currently_on(ThreadId::UI),
|
||||
0,
|
||||
"open must not be called on the UI thread"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
currently_on(ThreadId::IO),
|
||||
0,
|
||||
"open must not be called on the IO thread"
|
||||
);
|
||||
|
||||
// Continue the request immediately.
|
||||
if let Some(handle_request) = handle_request {
|
||||
*handle_request = 1;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
fn response_headers(
|
||||
&self,
|
||||
response: Option<&mut Response>,
|
||||
response_length: Option<&mut i64>,
|
||||
_redirect_url: Option<&mut CefString>,
|
||||
) {
|
||||
debug_assert_ne!(
|
||||
currently_on(ThreadId::IO),
|
||||
0,
|
||||
"response_headers must be called on the IO thread"
|
||||
);
|
||||
|
||||
let Some(response) = response else {
|
||||
return;
|
||||
};
|
||||
|
||||
response.set_status(self.status_code);
|
||||
response.set_status_text(Some(&CefString::from(self.status_text.as_str())));
|
||||
response.set_mime_type(Some(&CefString::from(self.mime_type.as_str())));
|
||||
|
||||
if let Some(mut header_map) = self.header_map.clone() {
|
||||
response.set_header_map(Some(&mut header_map));
|
||||
}
|
||||
|
||||
if let Some(response_length) = response_length {
|
||||
*response_length = if self.stream.is_some() { -1 } else { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn read(
|
||||
&self,
|
||||
data_out: *mut u8,
|
||||
bytes_to_read: i32,
|
||||
bytes_read: Option<&mut i32>,
|
||||
_callback: Option<&mut ResourceReadCallback>,
|
||||
) -> i32 {
|
||||
debug_assert_eq!(
|
||||
currently_on(ThreadId::UI),
|
||||
0,
|
||||
"read must not be called on the UI thread"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
currently_on(ThreadId::IO),
|
||||
0,
|
||||
"read must not be called on the IO thread"
|
||||
);
|
||||
if bytes_to_read < 1 {
|
||||
return 0;
|
||||
}
|
||||
let Some(bytes_read) = bytes_read else {
|
||||
return 0;
|
||||
};
|
||||
let Some(stream) = &self.stream else {
|
||||
*bytes_read = 0;
|
||||
return 1;
|
||||
};
|
||||
|
||||
// Read until the buffer is full or until read() returns 0 to indicate no more data.
|
||||
*bytes_read = 0;
|
||||
loop {
|
||||
let data_out = unsafe { data_out.add(*bytes_read as usize) };
|
||||
let read = stream.read(data_out, 1, (bytes_to_read - *bytes_read) as usize);
|
||||
*bytes_read += read as i32;
|
||||
if read == 0 || *bytes_read >= bytes_to_read {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if *bytes_read > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamResourceHandler {
|
||||
pub fn new_with_stream(mime_type: String, stream: StreamReader) -> ResourceHandler {
|
||||
Self::new(200, "OK".to_string(), mime_type, None, Some(stream))
|
||||
}
|
||||
}
|
||||
131
cef/src/wrapper/zip_archive.rs
Normal file
131
cef/src/wrapper/zip_archive.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use super::byte_read_handler::*;
|
||||
use crate::*;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub trait File: Send {
|
||||
fn stream_reader(&self) -> Option<StreamReader>;
|
||||
}
|
||||
|
||||
pub type FileMap = BTreeMap<String, Arc<Mutex<dyn File>>>;
|
||||
|
||||
struct ZipFile {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl File for ZipFile {
|
||||
fn stream_reader(&self) -> Option<StreamReader> {
|
||||
let mut handler =
|
||||
ByteReadHandler::new(Arc::new(Mutex::new(ByteStream::new(self.data.clone()))));
|
||||
stream_reader_create_for_handler(Some(&mut handler))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ZipArchive {
|
||||
contents: Mutex<FileMap>,
|
||||
}
|
||||
|
||||
impl ZipArchive {
|
||||
pub fn load(
|
||||
&self,
|
||||
stream: &mut StreamReader,
|
||||
password: &str,
|
||||
overwrite_existing: bool,
|
||||
) -> usize {
|
||||
let Ok(mut contents) = self.contents.lock() else {
|
||||
return 0;
|
||||
};
|
||||
let Some(reader) = zip_reader_create(Some(stream)) else {
|
||||
return 0;
|
||||
};
|
||||
let password = CefString::from(password);
|
||||
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let size = reader.file_size();
|
||||
if size <= 0 {
|
||||
// Skip directories and empty files.
|
||||
continue;
|
||||
}
|
||||
let size = size as usize;
|
||||
|
||||
if reader.open_file(Some(&password)) == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let name = CefString::from(&reader.file_name())
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
|
||||
if contents.contains_key(&name) {
|
||||
if overwrite_existing {
|
||||
contents.remove(&name);
|
||||
} else {
|
||||
// Skip files that already exist.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = Vec::with_capacity(size);
|
||||
while data.len() < size && reader.eof() == 0 {
|
||||
let mut chunk = vec![0; size - data.len()];
|
||||
let read = reader.read_file(Some(&mut chunk));
|
||||
if read <= 0 {
|
||||
break;
|
||||
}
|
||||
data.extend_from_slice(&chunk[..read as usize]);
|
||||
}
|
||||
|
||||
debug_assert_eq!(data.len(), size);
|
||||
reader.close_file();
|
||||
count += 1;
|
||||
|
||||
// Add the file to the map.
|
||||
contents.insert(name, Arc::new(Mutex::new(ZipFile { data })));
|
||||
|
||||
if reader.move_to_next_file() == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let Ok(mut contents) = self.contents.lock() else {
|
||||
return;
|
||||
};
|
||||
contents.clear();
|
||||
}
|
||||
|
||||
pub fn file_count(&self) -> usize {
|
||||
let Ok(contents) = self.contents.lock() else {
|
||||
return 0;
|
||||
};
|
||||
contents.len()
|
||||
}
|
||||
|
||||
pub fn file(&self, name: &str) -> Option<Arc<Mutex<dyn File>>> {
|
||||
let Ok(contents) = self.contents.lock() else {
|
||||
return None;
|
||||
};
|
||||
contents.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn remove_file(&self, name: &str) -> bool {
|
||||
let Ok(mut contents) = self.contents.lock() else {
|
||||
return false;
|
||||
};
|
||||
contents.remove(name).is_some()
|
||||
}
|
||||
|
||||
pub fn files(&self) -> FileMap {
|
||||
let Ok(contents) = self.contents.lock() else {
|
||||
return Default::default();
|
||||
};
|
||||
contents.clone()
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,7 @@ impl CefVersion {
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut result = self.download_archive_from(&url, &location, show_progress);
|
||||
let mut result = self.download_archive_from(url, &location, show_progress);
|
||||
|
||||
let mut retry = 0;
|
||||
while let Err(Error::Io(_)) = &result {
|
||||
@@ -368,7 +368,7 @@ impl CefVersion {
|
||||
retry += 1;
|
||||
thread::sleep(retry_delay * retry);
|
||||
|
||||
result = self.download_archive_from(&url, &location, show_progress);
|
||||
result = self.download_archive_from(url, &location, show_progress);
|
||||
}
|
||||
|
||||
result
|
||||
@@ -459,7 +459,7 @@ where
|
||||
println!("Downloading CEF archive for {target}...");
|
||||
}
|
||||
|
||||
let index = CefIndex::download_from(&url)?;
|
||||
let index = CefIndex::download_from(url)?;
|
||||
let platform = index.platform(target)?;
|
||||
let version = platform.version(cef_version)?;
|
||||
|
||||
|
||||
@@ -3,27 +3,55 @@ name = "cefsimple"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[[bin]]
|
||||
name = "cefsimple"
|
||||
|
||||
[[bin]]
|
||||
name = "cefsimple_helper"
|
||||
path = "src/mac/helper.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "bundle_cefsimple"
|
||||
path = "src/mac/bundle_cefsimple.rs"
|
||||
|
||||
[features]
|
||||
default = ["sandbox"]
|
||||
sandbox = ["cef/sandbox"]
|
||||
default = [
|
||||
"sandbox",
|
||||
]
|
||||
sandbox = [
|
||||
"cef/sandbox",
|
||||
]
|
||||
linux-x11 = [
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[package.metadata.cef.bundle]
|
||||
helper_name = "cefsimple_helper"
|
||||
resources_path = "resources"
|
||||
|
||||
[dependencies]
|
||||
cef.workspace = true
|
||||
cef-dll-sys.workspace = true
|
||||
cef = { workspace = true, features = [
|
||||
"build-util",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
plist.workspace = true
|
||||
serde.workspace = true
|
||||
objc2.workspace = true
|
||||
objc2-app-kit = { workspace = true, features = ["NSApplication", "NSResponder"] }
|
||||
objc2-foundation.workspace = true
|
||||
objc2-app-kit = { workspace = true, features = [
|
||||
"NSApplication",
|
||||
"NSEvent",
|
||||
"NSResponder",
|
||||
"NSUserInterfaceValidation",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys = { workspace = true, features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
winres.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
x11-dl = { workspace = true, optional = true }
|
||||
|
||||
14
examples/cefsimple/build.rs
Normal file
14
examples/cefsimple/build.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
include!("src/shared/resources.rs");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn main() {
|
||||
winres::WindowsResource::new()
|
||||
.set_icon_with_id("resources/win/cefsimple.ico", &IDI_CEFSIMPLE.to_string())
|
||||
.set_icon_with_id("resources/win/small.ico", &IDI_SMALL.to_string())
|
||||
.compile()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn main() {}
|
||||
@@ -0,0 +1,3 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
NSHumanReadableCopyright = "© Chromium Embedded Framework Authors, 2013";
|
||||
330
examples/cefsimple/resources/mac/English.lproj/MainMenu.xib
Normal file
330
examples/cefsimple/resources/mac/English.lproj/MainMenu.xib
Normal file
@@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="16B2657" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<dependencies>
|
||||
<deployment version="1090" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application"/>
|
||||
<menu title="AMainMenu" systemMenu="main" id="29" userLabel="MainMenu">
|
||||
<items>
|
||||
<menuItem title="cefsimple" id="56">
|
||||
<menu key="submenu" title="TestShell" systemMenu="apple" id="57">
|
||||
<items>
|
||||
<menuItem title="About cefsimple" id="58">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="236">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="129" userLabel="121"/>
|
||||
<menuItem isSeparatorItem="YES" id="143">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Services" id="131">
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="144">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Hide cefsimple" keyEquivalent="h" id="134">
|
||||
<connections>
|
||||
<action selector="hide:" target="-1" id="367"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="145">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="-1" id="368"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="150">
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="-1" id="370"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="149">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Quit cefsimple" keyEquivalent="q" id="136" userLabel="1111">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="369"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="File" id="83">
|
||||
<menu key="submenu" title="File" id="81">
|
||||
<items>
|
||||
<menuItem title="New" keyEquivalent="n" id="82" userLabel="9">
|
||||
<connections>
|
||||
<action selector="newDocument:" target="-1" id="373"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Open…" keyEquivalent="o" id="72">
|
||||
<connections>
|
||||
<action selector="openDocument:" target="-1" id="374"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Open Recent" id="124">
|
||||
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="125">
|
||||
<items>
|
||||
<menuItem title="Clear Menu" id="126">
|
||||
<connections>
|
||||
<action selector="clearRecentDocuments:" target="-1" id="127"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="79" userLabel="7">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Close" keyEquivalent="w" id="73" userLabel="1">
|
||||
<connections>
|
||||
<action selector="performClose:" target="-1" id="193"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Save" keyEquivalent="s" id="75" userLabel="3">
|
||||
<connections>
|
||||
<action selector="saveDocument:" target="-1" id="362"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Save As…" keyEquivalent="S" id="80" userLabel="8">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="saveDocumentAs:" target="-1" id="363"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Revert to Saved" id="112" userLabel="10">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="revertDocumentToSaved:" target="-1" id="364"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="74" userLabel="2">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Page Setup..." keyEquivalent="P" id="77" userLabel="5">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="runPageLayout:" target="-1" id="87"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Print…" keyEquivalent="p" id="78" userLabel="6">
|
||||
<connections>
|
||||
<action selector="print:" target="-1" id="86"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="217">
|
||||
<menu key="submenu" title="Edit" id="205">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="207">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="223"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="215">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="231"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="206">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="199">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="228"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="197">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="224"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="203">
|
||||
<connections>
|
||||
<action selector="paste:" target="-1" id="226"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="202">
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="235"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="198">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="232"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="214">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Find" id="218">
|
||||
<menu key="submenu" title="Find" id="220">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="37000" keyEquivalent="f" id="209">
|
||||
<connections>
|
||||
<action selector="commandDispatch:" target="-1" id="241"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="208"/>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="213">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="221"/>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="210">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="245"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Spelling and Grammar" id="216">
|
||||
<menu key="submenu" title="Spelling and Grammar" id="200">
|
||||
<items>
|
||||
<menuItem title="Show Spelling…" keyEquivalent=":" id="204">
|
||||
<connections>
|
||||
<action selector="showGuessPanel:" target="-1" id="230"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Spelling" keyEquivalent=";" id="201">
|
||||
<connections>
|
||||
<action selector="checkSpelling:" target="-1" id="225"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Spelling While Typing" id="219">
|
||||
<connections>
|
||||
<action selector="toggleContinuousSpellChecking:" target="-1" id="222"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Grammar With Spelling" id="346">
|
||||
<connections>
|
||||
<action selector="toggleGrammarChecking:" target="-1" id="347"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Substitutions" id="348">
|
||||
<menu key="submenu" title="Substitutions" id="349">
|
||||
<items>
|
||||
<menuItem title="Smart Copy/Paste" tag="1" keyEquivalent="f" id="350">
|
||||
<connections>
|
||||
<action selector="toggleSmartInsertDelete:" target="-1" id="355"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Quotes" tag="2" keyEquivalent="g" id="351">
|
||||
<connections>
|
||||
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="356"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Links" tag="3" keyEquivalent="G" id="354">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticLinkDetection:" target="-1" id="357"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Speech" id="211">
|
||||
<menu key="submenu" title="Speech" id="212">
|
||||
<items>
|
||||
<menuItem title="Start Speaking" id="196">
|
||||
<connections>
|
||||
<action selector="startSpeaking:" target="-1" id="233"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Stop Speaking" id="195">
|
||||
<connections>
|
||||
<action selector="stopSpeaking:" target="-1" id="227"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Format" id="299">
|
||||
<menu key="submenu" title="Format" id="300">
|
||||
<items>
|
||||
<menuItem title="Show Fonts" keyEquivalent="t" id="344"/>
|
||||
<menuItem title="Show Colors" keyEquivalent="C" id="345">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="orderFrontColorPanel:" target="-1" id="361"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="View" id="295">
|
||||
<menu key="submenu" title="View" id="296">
|
||||
<items>
|
||||
<menuItem title="Show Toolbar" keyEquivalent="t" id="297">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleToolbarShown:" target="-1" id="366"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Customize Toolbar…" id="298">
|
||||
<connections>
|
||||
<action selector="runToolbarCustomizationPalette:" target="-1" id="365"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="19">
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="24">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="23">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="-1" id="37"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="239">
|
||||
<connections>
|
||||
<action selector="performZoom:" target="-1" id="240"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="92">
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</menuItem>
|
||||
<menuItem title="Bring All to Front" id="5">
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="39"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="103" userLabel="1">
|
||||
<menu key="submenu" title="Help" id="106" userLabel="2">
|
||||
<items>
|
||||
<menuItem title="cefsimple Help" keyEquivalent="?" id="111">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="-1" id="360"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<userDefaultsController representsSharedInstance="YES" id="389"/>
|
||||
</objects>
|
||||
</document>
|
||||
BIN
examples/cefsimple/resources/mac/cefsimple.icns
Normal file
BIN
examples/cefsimple/resources/mac/cefsimple.icns
Normal file
Binary file not shown.
BIN
examples/cefsimple/resources/win/cefsimple.ico
Normal file
BIN
examples/cefsimple/resources/win/cefsimple.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
examples/cefsimple/resources/win/small.ico
Normal file
BIN
examples/cefsimple/resources/win/small.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
4
examples/cefsimple/src/lib.rs
Normal file
4
examples/cefsimple/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[cfg(all(target_os = "windows", feature = "sandbox"))]
|
||||
pub mod shared;
|
||||
#[cfg(all(target_os = "windows", feature = "sandbox"))]
|
||||
mod win;
|
||||
@@ -1,182 +0,0 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac {
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct InfoPlist {
|
||||
#[serde(rename = "CFBundleDevelopmentRegion")]
|
||||
cf_bundle_development_region: String,
|
||||
#[serde(rename = "CFBundleDisplayName")]
|
||||
cf_bundle_display_name: String,
|
||||
#[serde(rename = "CFBundleExecutable")]
|
||||
cf_bundle_executable: String,
|
||||
#[serde(rename = "CFBundleIdentifier")]
|
||||
cf_bundle_identifier: String,
|
||||
#[serde(rename = "CFBundleInfoDictionaryVersion")]
|
||||
cf_bundle_info_dictionary_version: String,
|
||||
#[serde(rename = "CFBundleName")]
|
||||
cf_bundle_name: String,
|
||||
#[serde(rename = "CFBundlePackageType")]
|
||||
cf_bundle_package_type: String,
|
||||
#[serde(rename = "CFBundleSignature")]
|
||||
cf_bundle_signature: String,
|
||||
#[serde(rename = "CFBundleVersion")]
|
||||
cf_bundle_version: String,
|
||||
#[serde(rename = "CFBundleShortVersionString")]
|
||||
cf_bundle_short_version_string: String,
|
||||
#[serde(rename = "LSEnvironment")]
|
||||
ls_environment: HashMap<String, String>,
|
||||
#[serde(rename = "LSFileQuarantineEnabled")]
|
||||
ls_file_quarantine_enabled: bool,
|
||||
#[serde(rename = "LSMinimumSystemVersion")]
|
||||
ls_minimum_system_version: String,
|
||||
#[serde(rename = "LSUIElement")]
|
||||
ls_ui_element: Option<String>,
|
||||
#[serde(rename = "NSBluetoothAlwaysUsageDescription")]
|
||||
ns_bluetooth_always_usage_description: String,
|
||||
#[serde(rename = "NSSupportsAutomaticGraphicsSwitching")]
|
||||
ns_supports_automatic_graphics_switching: bool,
|
||||
#[serde(rename = "NSWebBrowserPublicKeyCredentialUsageDescription")]
|
||||
ns_web_browser_publickey_credential_usage_description: String,
|
||||
#[serde(rename = "NSCameraUsageDescription")]
|
||||
ns_camera_usage_description: String,
|
||||
#[serde(rename = "NSMicrophoneUsageDescription")]
|
||||
ns_microphone_usage_description: String,
|
||||
}
|
||||
|
||||
const EXEC_PATH: &str = "Contents/MacOS";
|
||||
const FRAMEWORKS_PATH: &str = "Contents/Frameworks";
|
||||
const RESOURCES_PATH: &str = "Contents/Resources";
|
||||
const FRAMEWORK: &str = "Chromium Embedded Framework.framework";
|
||||
const HELPERS: &[&str] = &[
|
||||
"cefsimple Helper (GPU)",
|
||||
"cefsimple Helper (Renderer)",
|
||||
"cefsimple Helper (Plugin)",
|
||||
"cefsimple Helper (Alerts)",
|
||||
"cefsimple Helper",
|
||||
];
|
||||
|
||||
fn create_app_layout(app_path: &Path) -> PathBuf {
|
||||
[EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH]
|
||||
.iter()
|
||||
.for_each(|p| fs::create_dir_all(app_path.join(p)).unwrap());
|
||||
app_path.join("Contents")
|
||||
}
|
||||
|
||||
fn create_app(app_path: &Path, exec_name: &str, bin: &Path, is_helper: bool) -> PathBuf {
|
||||
let app_path = app_path.join(exec_name).with_extension("app");
|
||||
let contents_path = create_app_layout(&app_path);
|
||||
create_info_plist(&contents_path, exec_name, is_helper).unwrap();
|
||||
fs::copy(bin, app_path.join(EXEC_PATH).join(exec_name)).unwrap();
|
||||
app_path
|
||||
}
|
||||
|
||||
// See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-macos
|
||||
fn bundle(app_path: &Path) {
|
||||
let example_path = PathBuf::from(app_path);
|
||||
let main_app_path = create_app(
|
||||
app_path,
|
||||
"cefsimple",
|
||||
&example_path.join("cefsimple"),
|
||||
false,
|
||||
);
|
||||
let cef_path = cef_dll_sys::get_cef_dir().unwrap();
|
||||
let to = main_app_path.join(FRAMEWORKS_PATH).join(FRAMEWORK);
|
||||
if to.exists() {
|
||||
fs::remove_dir_all(&to).unwrap();
|
||||
}
|
||||
copy_directory(&cef_path.join(FRAMEWORK), &to);
|
||||
HELPERS.iter().for_each(|helper| {
|
||||
create_app(
|
||||
&main_app_path.join(FRAMEWORKS_PATH),
|
||||
helper,
|
||||
&example_path.join("cefsimple_helper"),
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn create_info_plist(
|
||||
contents_path: &Path,
|
||||
exec_name: &str,
|
||||
is_helper: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let info_plist = InfoPlist {
|
||||
cf_bundle_development_region: "en".to_string(),
|
||||
cf_bundle_display_name: exec_name.to_string(),
|
||||
cf_bundle_executable: exec_name.to_string(),
|
||||
cf_bundle_identifier: "org.cef-rs.cefsimple.helper".to_string(),
|
||||
cf_bundle_info_dictionary_version: "6.0".to_string(),
|
||||
cf_bundle_name: "cef-rs".to_string(),
|
||||
cf_bundle_package_type: "APPL".to_string(),
|
||||
cf_bundle_signature: "????".to_string(),
|
||||
cf_bundle_version: "1.0.0".to_string(),
|
||||
cf_bundle_short_version_string: "1.0".to_string(),
|
||||
ls_environment: [("MallocNanoZone".to_string(), "0".to_string())]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
ls_file_quarantine_enabled: true,
|
||||
ls_minimum_system_version: "11.0".to_string(),
|
||||
ls_ui_element: if is_helper {
|
||||
Some("1".to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
ns_bluetooth_always_usage_description: exec_name.to_string(),
|
||||
ns_supports_automatic_graphics_switching: true,
|
||||
ns_web_browser_publickey_credential_usage_description: exec_name.to_string(),
|
||||
ns_camera_usage_description: exec_name.to_string(),
|
||||
ns_microphone_usage_description: exec_name.to_string(),
|
||||
};
|
||||
|
||||
plist::to_file_xml(contents_path.join("Info.plist"), &info_plist)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_directory(src: &Path, dst: &Path) {
|
||||
fs::create_dir_all(dst).unwrap();
|
||||
for entry in fs::read_dir(src).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let dst_path = dst.join(entry.file_name());
|
||||
if entry.file_type().unwrap().is_dir() {
|
||||
copy_directory(&entry.path(), &dst_path);
|
||||
} else {
|
||||
fs::copy(entry.path(), &dst_path).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_command(args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let status = Command::new("cargo")
|
||||
.args(args)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let app_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/debug");
|
||||
run_command(&["build", "--bin", "cefsimple"])?;
|
||||
run_command(&["build", "--bin", "cefsimple_helper"])?;
|
||||
bundle(&app_path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
mac::main()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn main() {}
|
||||
243
examples/cefsimple/src/mac/mod.rs
Normal file
243
examples/cefsimple/src/mac/mod.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use crate::shared::simple_handler::*;
|
||||
use cef::application_mac::{CefAppProtocol, CrAppControlProtocol, CrAppProtocol};
|
||||
use objc2::{
|
||||
ClassType, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, extern_methods,
|
||||
msg_send,
|
||||
rc::Retained,
|
||||
runtime::{AnyObject, Bool, NSObject, NSObjectProtocol, ProtocolObject},
|
||||
sel,
|
||||
};
|
||||
use objc2_app_kit::{
|
||||
NSApp, NSApplication, NSApplicationDelegate, NSApplicationTerminateReply, NSEvent,
|
||||
NSUserInterfaceValidations, NSValidatedUserInterfaceItem,
|
||||
};
|
||||
use objc2_foundation::{NSBundle, NSObjectNSThreadPerformAdditions, ns_string};
|
||||
use std::{cell::Cell, ptr};
|
||||
|
||||
define_class! {
|
||||
#[unsafe(super(NSObject))]
|
||||
#[thread_kind = MainThreadOnly]
|
||||
pub struct SimpleAppDelegate;
|
||||
|
||||
impl SimpleAppDelegate {
|
||||
/// Create the application on the UI thread.
|
||||
#[unsafe(method(createApplication:))]
|
||||
unsafe fn create_application(&self, _object: Option<&AnyObject>) {
|
||||
let app = NSApp(MainThreadMarker::new().expect("Not running on the main thread"));
|
||||
assert!(app.isKindOfClass(SimpleApplication::class()));
|
||||
assert!(
|
||||
app.delegate()
|
||||
.unwrap()
|
||||
.isKindOfClass(SimpleAppDelegate::class())
|
||||
);
|
||||
|
||||
let main_bundle = NSBundle::mainBundle();
|
||||
let _: Bool = msg_send![&main_bundle,
|
||||
loadNibNamed: ns_string!("MainMenu"),
|
||||
owner: &*app,
|
||||
topLevelObjects: ptr::null_mut::<*const AnyObject>()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for SimpleAppDelegate {}
|
||||
|
||||
unsafe impl NSApplicationDelegate for SimpleAppDelegate {
|
||||
#[unsafe(method(applicationShouldTerminate:))]
|
||||
unsafe fn application_should_terminate(&self, _sender: &NSApplication) -> NSApplicationTerminateReply {
|
||||
NSApplicationTerminateReply::TerminateNow
|
||||
}
|
||||
|
||||
/// Called when the user clicks the app dock icon while the application is
|
||||
/// already running.
|
||||
#[unsafe(method(applicationShouldHandleReopen:hasVisibleWindows:))]
|
||||
unsafe fn application_should_handle_reopen(&self, _sender: &NSApplication, _has_visible_windows: Bool) -> Bool {
|
||||
if let Some(handler) = SimpleHandler::instance() {
|
||||
let mut handler = handler.lock().expect("Failed to lock SimpleHandler");
|
||||
if !handler.is_closing() {
|
||||
handler.show_main_window();
|
||||
}
|
||||
}
|
||||
Bool::NO
|
||||
}
|
||||
|
||||
/// Requests that any state restoration archive be created with secure encoding
|
||||
/// (macOS 12+ only). See https://crrev.com/c737387656 for details. This also
|
||||
/// fixes an issue with macOS default behavior incorrectly restoring windows
|
||||
/// after hard reset (holding down the power button).
|
||||
#[unsafe(method(applicationSupportsSecureRestorableState:))]
|
||||
unsafe fn application_supports_secure_restorable_state(&self, _sender: &NSApplication) -> Bool {
|
||||
Bool::YES
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSUserInterfaceValidations for SimpleAppDelegate {
|
||||
#[unsafe(method(validateUserInterfaceItem:))]
|
||||
unsafe fn validate_user_interface_item(&self, item: &ProtocolObject<dyn NSValidatedUserInterfaceItem>) -> Bool {
|
||||
const IDC_FIND: isize = 37000;
|
||||
|
||||
let tag = item.tag();
|
||||
if tag == IDC_FIND {
|
||||
Bool::YES
|
||||
} else {
|
||||
Bool::NO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleAppDelegate {
|
||||
fn new(mtm: MainThreadMarker) -> Retained<Self> {
|
||||
let this = SimpleAppDelegate::alloc(mtm).set_ivars(());
|
||||
unsafe { msg_send![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
||||
/// Instance variables of `SimpleApplication`.
|
||||
#[derive(Default)]
|
||||
pub struct SimpleApplicationIvars {
|
||||
handling_send_event: Cell<Bool>,
|
||||
}
|
||||
|
||||
define_class!(
|
||||
/// A `NSApplication` subclass that implements the required CEF protocols.
|
||||
///
|
||||
/// This class provides the necessary `CefAppProtocol` conformance to
|
||||
/// ensure that events are handled correctly by the Chromium framework on macOS.
|
||||
#[unsafe(super(NSApplication))]
|
||||
#[ivars = SimpleApplicationIvars]
|
||||
pub struct SimpleApplication;
|
||||
|
||||
impl SimpleApplication {
|
||||
#[unsafe(method(sendEvent:))]
|
||||
unsafe fn send_event(&self, event: &NSEvent) {
|
||||
let was_sending_event = self.is_handling_send_event();
|
||||
if !was_sending_event {
|
||||
self.set_handling_send_event(true);
|
||||
}
|
||||
|
||||
let _: () = msg_send![super(self), sendEvent:event];
|
||||
|
||||
if !was_sending_event {
|
||||
self.set_handling_send_event(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
|
||||
/// includes the application menu's quit menu item and keyboard equivalent, the
|
||||
/// application's dock icon menu's quit menu item, "quit" (not "force quit") in
|
||||
/// the Activity Monitor, and quits triggered by user logout and system restart
|
||||
/// and shutdown.
|
||||
///
|
||||
/// The default |-terminate:| implementation ends the process by calling exit(),
|
||||
/// and thus never leaves the main run loop. This is unsuitable for Chromium
|
||||
/// since Chromium depends on leaving the main run loop to perform an orderly
|
||||
/// shutdown. We support the normal |-terminate:| interface by overriding the
|
||||
/// default implementation. Our implementation, which is very specific to the
|
||||
/// needs of Chromium, works by asking the application delegate to terminate
|
||||
/// using its |-tryToTerminateApplication:| method.
|
||||
///
|
||||
/// |-tryToTerminateApplication:| differs from the standard
|
||||
/// |-applicationShouldTerminate:| in that no special event loop is run in the
|
||||
/// case that immediate termination is not possible (e.g., if dialog boxes
|
||||
/// allowing the user to cancel have to be shown). Instead, this method tries to
|
||||
/// close all browsers by calling CloseBrowser(false) via
|
||||
/// ClientHandler::CloseAllBrowsers. Calling CloseBrowser will result in a call
|
||||
/// to ClientHandler::DoClose and execution of |-performClose:| on the NSWindow.
|
||||
/// DoClose sets a flag that is used to differentiate between new close events
|
||||
/// (e.g., user clicked the window close button) and in-progress close events
|
||||
/// (e.g., user approved the close window dialog). The NSWindowDelegate
|
||||
/// |-windowShouldClose:| method checks this flag and either calls
|
||||
/// CloseBrowser(false) in the case of a new close event or destructs the
|
||||
/// NSWindow in the case of an in-progress close event.
|
||||
/// ClientHandler::OnBeforeClose will be called after the CEF NSView hosted in
|
||||
/// the NSWindow is dealloc'ed.
|
||||
///
|
||||
/// After the final browser window has closed ClientHandler::OnBeforeClose will
|
||||
/// begin actual tear-down of the application by calling CefQuitMessageLoop.
|
||||
/// This ends the NSApplication event loop and execution then returns to the
|
||||
/// main() function for cleanup before application termination.
|
||||
///
|
||||
/// The standard |-applicationShouldTerminate:| is not supported, and code paths
|
||||
/// leading to it must be redirected.
|
||||
#[unsafe(method(terminate:))]
|
||||
unsafe fn terminate(&self, _sender: &AnyObject) {
|
||||
if let Some(handler) = SimpleHandler::instance() {
|
||||
let mut handler = handler.lock().expect("Failed to lock SimpleHandler");
|
||||
if !handler.is_closing() {
|
||||
handler.close_all_browsers(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl CrAppControlProtocol for SimpleApplication {
|
||||
#[unsafe(method(setHandlingSendEvent:))]
|
||||
unsafe fn _set_handling_send_event(&self, handling_send_event: Bool) {
|
||||
self.ivars().handling_send_event.set(handling_send_event);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl CrAppProtocol for SimpleApplication {
|
||||
#[unsafe(method(isHandlingSendEvent))]
|
||||
unsafe fn _is_handling_send_event(&self) -> Bool {
|
||||
self.ivars().handling_send_event.get()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl CefAppProtocol for SimpleApplication {}
|
||||
);
|
||||
|
||||
impl SimpleApplication {
|
||||
extern_methods! {
|
||||
#[unsafe(method(sharedApplication))]
|
||||
fn shared_application() -> Retained<Self>;
|
||||
|
||||
#[unsafe(method(setHandlingSendEvent:))]
|
||||
fn set_handling_send_event(&self, handling_send_event: bool);
|
||||
|
||||
#[unsafe(method(isHandlingSendEvent))]
|
||||
fn is_handling_send_event(&self) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_simple_application() {
|
||||
// Initialize the SimpleApplication instance.
|
||||
// SAFETY: mtm ensures that here is the main thread.
|
||||
let _ = SimpleApplication::shared_application();
|
||||
|
||||
// If there was an invocation to NSApp prior to here,
|
||||
// then the NSApp will not be a SimpleApplication.
|
||||
// The following assertion ensures that this doesn't happen.
|
||||
assert!(
|
||||
NSApp(MainThreadMarker::new().expect("Not running on the main thread"))
|
||||
.isKindOfClass(SimpleApplication::class())
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setup_simple_app_delegate() -> Retained<SimpleAppDelegate> {
|
||||
let mtm = MainThreadMarker::new().expect("Not running on the main thread");
|
||||
|
||||
// Create the application delegate.
|
||||
let simple_delegate = SimpleAppDelegate::new(mtm);
|
||||
let delegate_proto =
|
||||
ProtocolObject::<dyn NSApplicationDelegate>::from_retained(simple_delegate.clone());
|
||||
let app = NSApp(MainThreadMarker::new().expect("Not running on the main thread"));
|
||||
assert!(app.isKindOfClass(SimpleApplication::class()));
|
||||
app.setDelegate(Some(&delegate_proto));
|
||||
assert!(
|
||||
app.delegate()
|
||||
.unwrap()
|
||||
.isKindOfClass(SimpleAppDelegate::class())
|
||||
);
|
||||
|
||||
unsafe {
|
||||
simple_delegate.performSelectorOnMainThread_withObject_waitUntilDone(
|
||||
sel!(createApplication:),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
simple_delegate
|
||||
}
|
||||
@@ -1,230 +1,27 @@
|
||||
use cef::{args::Args, rc::*, *};
|
||||
use std::sync::{Arc, Mutex};
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), not(feature = "sandbox"), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
wrap_app! {
|
||||
struct DemoApp {
|
||||
window: Arc<Mutex<Option<Window>>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(DemoBrowserProcessHandler::new(
|
||||
self.window.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_browser_process_handler! {
|
||||
struct DemoBrowserProcessHandler {
|
||||
window: Arc<Mutex<Option<Window>>>,
|
||||
}
|
||||
|
||||
impl BrowserProcessHandler {
|
||||
// The real lifespan of cef starts from `on_context_initialized`, so all the cef objects should be manipulated after that.
|
||||
fn on_context_initialized(&self) {
|
||||
println!("cef context intiialized");
|
||||
let mut client = DemoClient::new();
|
||||
let url = CefString::from("https://www.google.com");
|
||||
|
||||
let browser_view = browser_view_create(
|
||||
Some(&mut client),
|
||||
Some(&url),
|
||||
Some(&Default::default()),
|
||||
Option::<&mut DictionaryValue>::None,
|
||||
Option::<&mut RequestContext>::None,
|
||||
Option::<&mut BrowserViewDelegate>::None,
|
||||
)
|
||||
.expect("Failed to create browser view");
|
||||
|
||||
let mut delegate = DemoWindowDelegate::new(browser_view);
|
||||
if let Ok(mut window) = self.window.lock() {
|
||||
*window = Some(
|
||||
window_create_top_level(Some(&mut delegate)).expect("Failed to create window"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_client! {
|
||||
struct DemoClient;
|
||||
impl Client {}
|
||||
}
|
||||
|
||||
wrap_window_delegate! {
|
||||
struct DemoWindowDelegate {
|
||||
browser_view: BrowserView,
|
||||
}
|
||||
|
||||
impl ViewDelegate {
|
||||
fn on_child_view_changed(
|
||||
&self,
|
||||
_view: Option<&mut View>,
|
||||
_added: ::std::os::raw::c_int,
|
||||
_child: Option<&mut View>,
|
||||
) {
|
||||
// view.as_panel().map(|x| x.as_window().map(|w| w.close()));
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelDelegate {}
|
||||
|
||||
impl WindowDelegate {
|
||||
fn on_window_created(&self, window: Option<&mut Window>) {
|
||||
if let Some(window) = window {
|
||||
let view = self.browser_view.clone();
|
||||
window.add_child_view(Some(&mut (&view).into()));
|
||||
window.show();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_window_destroyed(&self, _window: Option<&mut Window>) {
|
||||
quit_message_loop();
|
||||
}
|
||||
|
||||
fn with_standard_window_buttons(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int {
|
||||
1
|
||||
}
|
||||
|
||||
fn can_resize(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int {
|
||||
1
|
||||
}
|
||||
|
||||
fn can_maximize(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int {
|
||||
1
|
||||
}
|
||||
|
||||
fn can_minimize(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int {
|
||||
1
|
||||
}
|
||||
|
||||
fn can_close(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Rewrite this demo based on cef/tests/cefsimple
|
||||
fn main() {
|
||||
#[cfg(target_os = "macos")]
|
||||
let _loader = {
|
||||
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||
assert!(loader.load());
|
||||
loader
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use objc2::{
|
||||
ClassType, MainThreadMarker, msg_send,
|
||||
rc::Retained,
|
||||
runtime::{AnyObject, NSObjectProtocol},
|
||||
};
|
||||
use objc2_app_kit::NSApp;
|
||||
|
||||
use application::SimpleApplication;
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
|
||||
unsafe {
|
||||
// Initialize the SimpleApplication instance.
|
||||
// SAFETY: mtm ensures that here is the main thread.
|
||||
let _: Retained<AnyObject> = msg_send![SimpleApplication::class(), sharedApplication];
|
||||
}
|
||||
|
||||
// If there was an invocation to NSApp prior to here,
|
||||
// then the NSApp will not be a SimpleApplication.
|
||||
// The following assertion ensures that this doesn't happen.
|
||||
assert!(NSApp(mtm).isKindOfClass(SimpleApplication::class()));
|
||||
}
|
||||
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
|
||||
let args = Args::new();
|
||||
let cmd = args.as_cmd_line().unwrap();
|
||||
|
||||
let switch = CefString::from("type");
|
||||
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
|
||||
|
||||
let window = Arc::new(Mutex::new(None));
|
||||
let mut app = DemoApp::new(window.clone());
|
||||
|
||||
let ret = execute_process(
|
||||
Some(args.as_main_args()),
|
||||
Some(&mut app),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
if is_browser_process {
|
||||
println!("launch browser process");
|
||||
assert!(ret == -1, "cannot execute browser process");
|
||||
} else {
|
||||
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
|
||||
println!("launch process {process_type}");
|
||||
assert!(ret >= 0, "cannot execute non-browser process");
|
||||
// non-browser process does not initialize cef
|
||||
return;
|
||||
}
|
||||
let settings = Settings {
|
||||
no_sandbox: !cfg!(feature = "sandbox") as _,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
initialize(
|
||||
Some(args.as_main_args()),
|
||||
Some(&settings),
|
||||
Some(&mut app),
|
||||
std::ptr::null_mut(),
|
||||
),
|
||||
1
|
||||
);
|
||||
|
||||
run_message_loop();
|
||||
|
||||
let window = window.lock().expect("Failed to lock window");
|
||||
let window = window.as_ref().expect("Window is None");
|
||||
assert!(window.has_one_ref());
|
||||
|
||||
shutdown();
|
||||
}
|
||||
pub mod shared;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod application {
|
||||
use std::cell::Cell;
|
||||
mod mac;
|
||||
|
||||
use cef::application_mac::{CefAppProtocol, CrAppControlProtocol, CrAppProtocol};
|
||||
use objc2::{DefinedClass, define_class, runtime::Bool};
|
||||
use objc2_app_kit::NSApplication;
|
||||
#[cfg(not(all(feature = "sandbox", target_os = "windows")))]
|
||||
fn main() -> Result<(), &'static str> {
|
||||
let _library = shared::load_cef();
|
||||
|
||||
/// Instance variables of `SimpleApplication`.
|
||||
pub struct SimpleApplicationIvars {
|
||||
handling_send_event: Cell<Bool>,
|
||||
}
|
||||
let args = cef::args::Args::new();
|
||||
let Some(cmd_line) = args.as_cmd_line() else {
|
||||
return Err("Failed to parse command line arguments");
|
||||
};
|
||||
|
||||
define_class!(
|
||||
/// A `NSApplication` subclass that implements the required CEF protocols.
|
||||
///
|
||||
/// This class provides the necessary `CefAppProtocol` conformance to
|
||||
/// ensure that events are handled correctly by the Chromium framework on macOS.
|
||||
#[unsafe(super(NSApplication))]
|
||||
#[ivars = SimpleApplicationIvars]
|
||||
pub struct SimpleApplication;
|
||||
|
||||
unsafe impl CrAppControlProtocol for SimpleApplication {
|
||||
#[unsafe(method(setHandlingSendEvent:))]
|
||||
unsafe fn set_handling_send_event(&self, handling_send_event: Bool) {
|
||||
self.ivars().handling_send_event.set(handling_send_event);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl CrAppProtocol for SimpleApplication {
|
||||
#[unsafe(method(isHandlingSendEvent))]
|
||||
unsafe fn is_handling_send_event(&self) -> Bool {
|
||||
self.ivars().handling_send_event.get()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl CefAppProtocol for SimpleApplication {}
|
||||
);
|
||||
shared::run_main(args.as_main_args(), &cmd_line, std::ptr::null_mut());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "sandbox", target_os = "windows"))]
|
||||
fn main() -> Result<(), &'static str> {
|
||||
Err("Running in sandbox mode on Windows requires bootstrap.exe or bootstrapc.exe.")
|
||||
}
|
||||
|
||||
75
examples/cefsimple/src/shared/mod.rs
Normal file
75
examples/cefsimple/src/shared/mod.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
//! Rust port of the [`cefsimple`](https://github.com/chromiumembedded/cef/tree/master/tests/cefsimple) example.
|
||||
|
||||
use cef::*;
|
||||
|
||||
pub mod resources;
|
||||
pub mod simple_app;
|
||||
pub mod simple_handler;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub type Library = library_loader::LibraryLoader;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub type Library = ();
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn load_cef() -> Library {
|
||||
#[cfg(target_os = "macos")]
|
||||
let library = {
|
||||
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||
assert!(loader.load());
|
||||
loader
|
||||
};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let library = ();
|
||||
|
||||
// Initialize the CEF API version.
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::mac::setup_simple_application();
|
||||
|
||||
library
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn run_main(main_args: &MainArgs, cmd_line: &CommandLine, sandbox_info: *mut u8) {
|
||||
let switch = CefString::from("type");
|
||||
let is_browser_process = cmd_line.has_switch(Some(&switch)) != 1;
|
||||
|
||||
let ret = execute_process(Some(main_args), None, sandbox_info);
|
||||
|
||||
if is_browser_process {
|
||||
println!("launch browser process");
|
||||
assert_eq!(ret, -1, "cannot execute browser process");
|
||||
} else {
|
||||
let process_type = CefString::from(&cmd_line.switch_value(Some(&switch)));
|
||||
println!("launch process {process_type}");
|
||||
assert!(ret >= 0, "cannot execute non-browser process");
|
||||
// non-browser process does not initialize cef
|
||||
return;
|
||||
}
|
||||
|
||||
let mut app = simple_app::SimpleApp::new();
|
||||
|
||||
let settings = Settings {
|
||||
no_sandbox: !cfg!(feature = "sandbox") as _,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
initialize(
|
||||
Some(main_args),
|
||||
Some(&settings),
|
||||
Some(&mut app),
|
||||
sandbox_info,
|
||||
),
|
||||
1
|
||||
);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let _delegate = crate::mac::setup_simple_app_delegate();
|
||||
|
||||
run_message_loop();
|
||||
|
||||
shutdown();
|
||||
}
|
||||
2
examples/cefsimple/src/shared/resources.rs
Normal file
2
examples/cefsimple/src/shared/resources.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub const IDI_CEFSIMPLE: u16 = 120;
|
||||
pub const IDI_SMALL: u16 = 121;
|
||||
215
examples/cefsimple/src/shared/simple_app.rs
Normal file
215
examples/cefsimple/src/shared/simple_app.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use cef::*;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use super::simple_handler::*;
|
||||
|
||||
wrap_window_delegate! {
|
||||
struct SimpleWindowDelegate {
|
||||
browser_view: RefCell<Option<BrowserView>>,
|
||||
runtime_style: RuntimeStyle,
|
||||
initial_show_state: ShowState,
|
||||
}
|
||||
|
||||
impl ViewDelegate {
|
||||
fn preferred_size(&self, _view: Option<&mut View>) -> Size {
|
||||
Size {
|
||||
width: 800,
|
||||
height: 600,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelDelegate {}
|
||||
|
||||
impl WindowDelegate {
|
||||
fn on_window_created(&self, window: Option<&mut Window>) {
|
||||
// Add the browser view and show the window.
|
||||
let browser_view = self.browser_view.borrow();
|
||||
let (Some(window), Some(browser_view)) = (window, browser_view.as_ref()) else {
|
||||
return;
|
||||
};
|
||||
let mut view = View::from(browser_view);
|
||||
window.add_child_view(Some(&mut view));
|
||||
|
||||
if self.initial_show_state != ShowState::HIDDEN {
|
||||
window.show();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_window_destroyed(&self, _window: Option<&mut Window>) {
|
||||
let mut browser_view = self.browser_view.borrow_mut();
|
||||
*browser_view = None;
|
||||
}
|
||||
|
||||
fn can_close(&self, _window: Option<&mut Window>) -> i32 {
|
||||
// Allow the window to close if the browser says it's OK.
|
||||
let browser_view = self.browser_view.borrow();
|
||||
let browser_view = browser_view.as_ref().expect("BrowserView is None");
|
||||
if let Some(browser) = browser_view.browser() {
|
||||
let browser_host = browser.host().expect("BrowserHost is None");
|
||||
browser_host.try_close_browser()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_show_state(&self, _window: Option<&mut Window>) -> ShowState {
|
||||
self.initial_show_state
|
||||
}
|
||||
|
||||
fn window_runtime_style(&self) -> RuntimeStyle {
|
||||
self.runtime_style
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_browser_view_delegate! {
|
||||
struct SimpleBrowserViewDelegate {
|
||||
runtime_style: RuntimeStyle,
|
||||
}
|
||||
|
||||
impl ViewDelegate {}
|
||||
|
||||
impl BrowserViewDelegate {
|
||||
fn on_popup_browser_view_created(
|
||||
&self,
|
||||
_browser_view: Option<&mut BrowserView>,
|
||||
popup_browser_view: Option<&mut BrowserView>,
|
||||
_is_devtools: i32,
|
||||
) -> i32 {
|
||||
// Create a new top-level Window for the popup. It will show itself after
|
||||
// creation.
|
||||
let mut window_delegate = SimpleWindowDelegate::new(
|
||||
RefCell::new(popup_browser_view.cloned()),
|
||||
self.runtime_style,
|
||||
ShowState::NORMAL,
|
||||
);
|
||||
window_create_top_level(Some(&mut window_delegate));
|
||||
|
||||
// We created the Window.
|
||||
1
|
||||
}
|
||||
|
||||
fn browser_runtime_style(&self) -> RuntimeStyle {
|
||||
self.runtime_style
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_app! {
|
||||
pub struct SimpleApp;
|
||||
|
||||
impl App {
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(SimpleBrowserProcessHandler::new(RefCell::new(None)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_browser_process_handler! {
|
||||
struct SimpleBrowserProcessHandler {
|
||||
client: RefCell<Option<Client>>,
|
||||
}
|
||||
|
||||
impl BrowserProcessHandler {
|
||||
fn on_context_initialized(&self) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
// Check if Alloy style will be used.
|
||||
let command_line = command_line_get_global().expect("Failed to get command line");
|
||||
let use_alloy_style =
|
||||
command_line.has_switch(Some(&CefString::from("use-alloy-style"))) != 0;
|
||||
let runtime_style = if use_alloy_style {
|
||||
RuntimeStyle::ALLOY
|
||||
} else {
|
||||
RuntimeStyle::DEFAULT
|
||||
};
|
||||
|
||||
{
|
||||
// SimpleHandler implements browser-level callbacks.
|
||||
let mut client = self.client.borrow_mut();
|
||||
*client = Some(SimpleHandlerClient::new(SimpleHandler::new(
|
||||
use_alloy_style,
|
||||
)));
|
||||
}
|
||||
|
||||
// Specify CEF browser settings here.
|
||||
let settings = BrowserSettings::default();
|
||||
|
||||
// Check if a "--url=" value was provided via the command-line. If so, use
|
||||
// that instead of the default URL.
|
||||
let url = CefString::from(&command_line.switch_value(Some(&CefString::from("url"))))
|
||||
.to_string();
|
||||
let url = if url.is_empty() {
|
||||
"https://www.google.com/"
|
||||
} else {
|
||||
url.as_str()
|
||||
};
|
||||
let url = CefString::from(url);
|
||||
|
||||
// Views is enabled by default (add `--use-native` to disable).
|
||||
let use_views = command_line.has_switch(Some(&CefString::from("use-native"))) != 0;
|
||||
|
||||
// If using Views create the browser using the Views framework, otherwise
|
||||
// create the browser using the native platform framework.
|
||||
if use_views {
|
||||
// Create the BrowserView.
|
||||
let mut client = self.default_client();
|
||||
let mut delegate = SimpleBrowserViewDelegate::new(runtime_style);
|
||||
let browser_view = browser_view_create(
|
||||
client.as_mut(),
|
||||
Some(&url),
|
||||
Some(&settings),
|
||||
None,
|
||||
None,
|
||||
Some(&mut delegate),
|
||||
);
|
||||
|
||||
// Optionally configure the initial show state.
|
||||
let initial_show_state = CefString::from(
|
||||
&command_line.switch_value(Some(&CefString::from("initial-show-state"))),
|
||||
)
|
||||
.to_string();
|
||||
let initial_show_state = match initial_show_state.as_str() {
|
||||
"minimized" => ShowState::MINIMIZED,
|
||||
"maximized" => ShowState::MAXIMIZED,
|
||||
// Hidden show state is only supported on MacOS.
|
||||
#[cfg(target_os = "macos")]
|
||||
"hidden" => ShowState::HIDDEN,
|
||||
_ => ShowState::NORMAL,
|
||||
};
|
||||
|
||||
// Create the Window. It will show itself after creation.
|
||||
let mut delegate = SimpleWindowDelegate::new(
|
||||
RefCell::new(browser_view),
|
||||
runtime_style,
|
||||
initial_show_state,
|
||||
);
|
||||
window_create_top_level(Some(&mut delegate));
|
||||
} else {
|
||||
// Information used when creating the native window.
|
||||
let window_info = WindowInfo {
|
||||
runtime_style,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let window_info = window_info.set_as_popup(Default::default(), "cefsimple");
|
||||
|
||||
let mut client = self.default_client();
|
||||
browser_host_create_browser(
|
||||
Some(&window_info),
|
||||
client.as_mut(),
|
||||
Some(&url),
|
||||
Some(&settings),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_client(&self) -> Option<Client> {
|
||||
self.client.borrow().clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
76
examples/cefsimple/src/shared/simple_handler/linux.rs
Normal file
76
examples/cefsimple/src/shared/simple_handler/linux.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use cef::*;
|
||||
|
||||
fn window_from_browser(browser: Option<&mut Browser>) -> Option<WindowHandle> {
|
||||
let window = browser?.host()?.window_handle();
|
||||
if window == 0 { None } else { Some(window) }
|
||||
}
|
||||
|
||||
pub fn platform_title_change(browser: Option<&mut Browser>, _title: Option<&CefString>) {
|
||||
// Retrieve the X11 display shared with Chromium.
|
||||
let display = get_xdisplay();
|
||||
if display.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the X11 window handle for the browser.
|
||||
let Some(_window) = window_from_browser(browser) else {
|
||||
return;
|
||||
};
|
||||
|
||||
#[cfg(feature = "linux-x11")]
|
||||
unsafe {
|
||||
use std::ffi::{CString, c_char};
|
||||
use x11_dl::xlib::*;
|
||||
|
||||
// Load the Xlib library dynamically.
|
||||
let Ok(xlib) = Xlib::open() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Retrieve the atoms required by the below XChangeProperty call.
|
||||
let Ok(names) = ["_NET_WM_NAME", "UTF8_STRING"]
|
||||
.into_iter()
|
||||
.map(CString::new)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let mut names: Vec<_> = names
|
||||
.iter()
|
||||
.map(|name| name.as_ptr() as *mut c_char)
|
||||
.collect();
|
||||
let mut atoms = [0; 2];
|
||||
let result = (xlib.XInternAtoms)(
|
||||
display as *mut _,
|
||||
names.as_mut_ptr(),
|
||||
2,
|
||||
0,
|
||||
atoms.as_mut_ptr(),
|
||||
);
|
||||
if result == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the window title.
|
||||
let Ok(title) = CString::new(_title.map(CefString::to_string).unwrap_or_default()) else {
|
||||
return;
|
||||
};
|
||||
let title = title.as_c_str();
|
||||
(xlib.XChangeProperty)(
|
||||
display as *mut _,
|
||||
_window,
|
||||
atoms[0],
|
||||
atoms[1],
|
||||
8,
|
||||
PropModeReplace,
|
||||
title.as_ptr() as *const _,
|
||||
title.count_bytes() as i32,
|
||||
);
|
||||
|
||||
// TODO(erg): This is technically wrong. So XStoreName and friends expect
|
||||
// this in Host Portable Character Encoding instead of UTF-8, which I believe
|
||||
// is Compound Text. This shouldn't matter 90% of the time since this is the
|
||||
// fallback to the UTF8 property above.
|
||||
(xlib.XStoreName)(display as *mut _, _window, title.as_ptr());
|
||||
}
|
||||
}
|
||||
29
examples/cefsimple/src/shared/simple_handler/mac.rs
Normal file
29
examples/cefsimple/src/shared/simple_handler/mac.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use cef::*;
|
||||
use objc2::{Message, rc::Retained};
|
||||
use objc2_app_kit::{NSView, NSWindow};
|
||||
use objc2_foundation::NSString;
|
||||
|
||||
fn window_from_browser(browser: Option<&mut Browser>) -> Option<Retained<NSWindow>> {
|
||||
let view_ptr = browser?.host()?.window_handle().cast::<NSView>();
|
||||
let view = unsafe { view_ptr.as_ref()? };
|
||||
let view = view.retain();
|
||||
view.window()
|
||||
}
|
||||
|
||||
pub fn platform_title_change(browser: Option<&mut Browser>, title: Option<&CefString>) {
|
||||
let Some(window) = window_from_browser(browser) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let title = title.map(CefString::to_string).unwrap_or_default();
|
||||
let title = NSString::from_str(&title);
|
||||
window.setTitle(&title);
|
||||
}
|
||||
|
||||
pub fn platform_show_window(browser: Option<&mut Browser>) {
|
||||
let Some(window) = window_from_browser(browser) else {
|
||||
return;
|
||||
};
|
||||
|
||||
window.makeKeyAndOrderFront(Some(&window));
|
||||
}
|
||||
326
examples/cefsimple/src/shared/simple_handler/mod.rs
Normal file
326
examples/cefsimple/src/shared/simple_handler/mod.rs
Normal file
@@ -0,0 +1,326 @@
|
||||
use cef::*;
|
||||
use std::sync::{Arc, Mutex, OnceLock, Weak};
|
||||
|
||||
fn get_data_uri(data: &[u8], mime_type: &str) -> String {
|
||||
let data = CefString::from(&base64_encode(Some(data)));
|
||||
let uri = CefString::from(&uriencode(Some(&data), 0)).to_string();
|
||||
format!("data:{mime_type};base64,{uri}")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac;
|
||||
#[cfg(target_os = "macos")]
|
||||
use mac::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod win;
|
||||
#[cfg(target_os = "windows")]
|
||||
use win::*;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
use linux::*;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn platform_show_window(_browser: Option<&mut Browser>) {
|
||||
todo!("Implement platform_show_window for non-macOS platforms");
|
||||
}
|
||||
|
||||
static SIMPLE_HANDLER_INSTANCE: OnceLock<Weak<Mutex<SimpleHandler>>> = OnceLock::new();
|
||||
|
||||
pub struct SimpleHandler {
|
||||
is_alloy_style: bool,
|
||||
browser_list: Vec<Browser>,
|
||||
is_closing: bool,
|
||||
weak_self: Weak<Mutex<Self>>,
|
||||
}
|
||||
|
||||
impl SimpleHandler {
|
||||
pub fn instance() -> Option<Arc<Mutex<Self>>> {
|
||||
SIMPLE_HANDLER_INSTANCE
|
||||
.get()
|
||||
.and_then(|weak| weak.upgrade())
|
||||
}
|
||||
|
||||
pub fn new(is_alloy_style: bool) -> Arc<Mutex<Self>> {
|
||||
Arc::new_cyclic(|weak| {
|
||||
if let Err(instance) = SIMPLE_HANDLER_INSTANCE.set(weak.clone()) {
|
||||
assert_eq!(instance.strong_count(), 0, "Replacing a viable instance");
|
||||
}
|
||||
|
||||
Mutex::new(Self {
|
||||
is_alloy_style,
|
||||
browser_list: Vec::new(),
|
||||
is_closing: false,
|
||||
weak_self: weak.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn on_title_change(&mut self, browser: Option<&mut Browser>, title: Option<&CefString>) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
let mut browser = browser.cloned();
|
||||
if let Some(browser_view) = browser_view_get_for_browser(browser.as_mut()) {
|
||||
if let Some(window) = browser_view.window() {
|
||||
window.set_title(title);
|
||||
}
|
||||
} else if self.is_alloy_style {
|
||||
platform_title_change(browser.as_mut(), title);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_after_created(&mut self, browser: Option<&mut Browser>) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
let browser = browser.cloned().expect("Browser is None");
|
||||
|
||||
// Sanity-check the configured runtime style.
|
||||
assert_eq!(
|
||||
browser.host().expect("BrowserHost is None").runtime_style(),
|
||||
if self.is_alloy_style {
|
||||
RuntimeStyle::ALLOY
|
||||
} else {
|
||||
RuntimeStyle::CHROME
|
||||
}
|
||||
);
|
||||
|
||||
// Add to the list of existing browsers.
|
||||
self.browser_list.push(browser);
|
||||
}
|
||||
|
||||
fn do_close(&mut self, _browser: Option<&mut Browser>) -> bool {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
// Closing the main window requires special handling. See the DoClose()
|
||||
// documentation in the CEF header for a detailed destription of this
|
||||
// process.
|
||||
if self.browser_list.len() == 1 {
|
||||
// Set a flag to indicate that the window close should be allowed.
|
||||
self.is_closing = true;
|
||||
}
|
||||
|
||||
// Allow the close. For windowed browsers this will result in the OS close
|
||||
// event being sent.
|
||||
false
|
||||
}
|
||||
|
||||
fn on_before_close(&mut self, browser: Option<&mut Browser>) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
// Remove from the list of existing browsers.
|
||||
let mut browser = browser.cloned().expect("Browser is None");
|
||||
if let Some(index) = self
|
||||
.browser_list
|
||||
.iter()
|
||||
.position(move |elem| elem.is_same(Some(&mut browser)) != 0)
|
||||
{
|
||||
self.browser_list.remove(index);
|
||||
}
|
||||
|
||||
if self.browser_list.is_empty() {
|
||||
// All browser windows have closed. Quit the application message loop.
|
||||
quit_message_loop();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_load_error(
|
||||
&mut self,
|
||||
_browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
error_code: Errorcode,
|
||||
error_text: Option<&CefString>,
|
||||
failed_url: Option<&CefString>,
|
||||
) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
// Allow Chrome to show the error page.
|
||||
if !self.is_alloy_style {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't display an error for downloaded files.
|
||||
let error_code = sys::cef_errorcode_t::from(error_code);
|
||||
if error_code == sys::cef_errorcode_t::ERR_ABORTED {
|
||||
return;
|
||||
}
|
||||
let error_code = error_code as i32;
|
||||
|
||||
let frame = frame.expect("Frame is None");
|
||||
|
||||
// Display a load error message using a data: URI.
|
||||
let error_text = error_text.map(CefString::to_string).unwrap_or_default();
|
||||
let failed_url = failed_url.map(CefString::to_string).unwrap_or_default();
|
||||
let data = format!(
|
||||
r#"
|
||||
<html>
|
||||
<body bgcolor="white">
|
||||
<h2>Failed to load URL {failed_url} with error {error_text} ({error_code}).</h2>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
);
|
||||
|
||||
let uri = get_data_uri(data.as_bytes(), "text/html");
|
||||
let uri = CefString::from(uri.as_str());
|
||||
frame.load_url(Some(&uri));
|
||||
}
|
||||
|
||||
pub fn show_main_window(&mut self) {
|
||||
let thread_id = ThreadId::UI;
|
||||
if currently_on(thread_id) == 0 {
|
||||
// Execute on the UI thread.
|
||||
let this = self
|
||||
.weak_self
|
||||
.upgrade()
|
||||
.expect("Weak reference to SimpleHandler is None");
|
||||
let mut task = ShowMainWindow::new(this);
|
||||
post_task(thread_id, Some(&mut task));
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(mut main_browser) = self.browser_list.first().cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(browser_view) = browser_view_get_for_browser(Some(&mut main_browser)) {
|
||||
// Show the window using the Views framework.
|
||||
if let Some(window) = browser_view.window() {
|
||||
window.show();
|
||||
}
|
||||
} else if self.is_alloy_style {
|
||||
platform_show_window(Some(&mut main_browser));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_all_browsers(&mut self, force_close: bool) {
|
||||
let thread_id = ThreadId::UI;
|
||||
if currently_on(thread_id) == 0 {
|
||||
// Execute on the UI thread.
|
||||
let this = self
|
||||
.weak_self
|
||||
.upgrade()
|
||||
.expect("Weak reference to SimpleHandler is None");
|
||||
let mut task = CloseAllBrowsers::new(this, force_close);
|
||||
post_task(thread_id, Some(&mut task));
|
||||
return;
|
||||
}
|
||||
|
||||
for browser in self.browser_list.iter() {
|
||||
let browser_host = browser.host().expect("BrowserHost is None");
|
||||
browser_host.close_browser(force_close.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_closing(&self) -> bool {
|
||||
self.is_closing
|
||||
}
|
||||
}
|
||||
|
||||
wrap_client! {
|
||||
pub struct SimpleHandlerClient {
|
||||
inner: Arc<Mutex<SimpleHandler>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn display_handler(&self) -> Option<DisplayHandler> {
|
||||
Some(SimpleHandlerDisplayHandler::new(self.inner.clone()))
|
||||
}
|
||||
|
||||
fn life_span_handler(&self) -> Option<LifeSpanHandler> {
|
||||
Some(SimpleHandlerLifeSpanHandler::new(self.inner.clone()))
|
||||
}
|
||||
|
||||
fn load_handler(&self) -> Option<LoadHandler> {
|
||||
Some(SimpleHandlerLoadHandler::new(self.inner.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_display_handler! {
|
||||
struct SimpleHandlerDisplayHandler {
|
||||
inner: Arc<Mutex<SimpleHandler>>,
|
||||
}
|
||||
|
||||
impl DisplayHandler {
|
||||
fn on_title_change(&self, browser: Option<&mut Browser>, title: Option<&CefString>) {
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.on_title_change(browser, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_life_span_handler! {
|
||||
struct SimpleHandlerLifeSpanHandler {
|
||||
inner: Arc<Mutex<SimpleHandler>>,
|
||||
}
|
||||
|
||||
impl LifeSpanHandler {
|
||||
fn on_after_created(&self, browser: Option<&mut Browser>) {
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.on_after_created(browser);
|
||||
}
|
||||
|
||||
fn do_close(&self, browser: Option<&mut Browser>) -> i32 {
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.do_close(browser).into()
|
||||
}
|
||||
|
||||
fn on_before_close(&self, browser: Option<&mut Browser>) {
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.on_before_close(browser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_load_handler! {
|
||||
struct SimpleHandlerLoadHandler {
|
||||
inner: Arc<Mutex<SimpleHandler>>,
|
||||
}
|
||||
|
||||
impl LoadHandler {
|
||||
fn on_load_error(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
error_code: Errorcode,
|
||||
error_text: Option<&CefString>,
|
||||
failed_url: Option<&CefString>,
|
||||
) {
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.on_load_error(browser, frame, error_code, error_text, failed_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_task! {
|
||||
struct ShowMainWindow {
|
||||
inner: Arc<Mutex<SimpleHandler>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn execute(&self) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.show_main_window();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_task! {
|
||||
struct CloseAllBrowsers {
|
||||
inner: Arc<Mutex<SimpleHandler>>,
|
||||
force_close: bool,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn execute(&self) {
|
||||
debug_assert_ne!(currently_on(ThreadId::UI), 0);
|
||||
|
||||
let mut inner = self.inner.lock().expect("Failed to lock inner");
|
||||
inner.close_all_browsers(self.force_close);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
examples/cefsimple/src/shared/simple_handler/win.rs
Normal file
18
examples/cefsimple/src/shared/simple_handler/win.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use cef::*;
|
||||
use std::iter;
|
||||
use windows_sys::Win32::{Foundation::HWND, UI::WindowsAndMessaging::*};
|
||||
|
||||
fn window_from_browser(browser: Option<&mut Browser>) -> Option<HWND> {
|
||||
let window = browser?.host()?.window_handle().0;
|
||||
Some(window.cast())
|
||||
}
|
||||
|
||||
pub fn platform_title_change(browser: Option<&mut Browser>, title: Option<&CefString>) {
|
||||
let Some(window) = window_from_browser(browser) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let title = title.map(CefString::to_string).unwrap_or_default();
|
||||
let title: Vec<_> = title.encode_utf16().chain(iter::once(0)).collect();
|
||||
unsafe { SetWindowTextW(window, title.as_ptr()) };
|
||||
}
|
||||
21
examples/cefsimple/src/win.rs
Normal file
21
examples/cefsimple/src/win.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use crate::shared;
|
||||
use cef::*;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn RunWinMain(
|
||||
instance: sys::HINSTANCE,
|
||||
_command_line: *const u8,
|
||||
_command_show: i32,
|
||||
sandbox_info: *mut u8,
|
||||
) -> i32 {
|
||||
let _library = shared::load_cef();
|
||||
|
||||
let main_args = MainArgs { instance };
|
||||
let args = args::Args::from(main_args);
|
||||
let Some(cmd_line) = args.as_cmd_line() else {
|
||||
return 1;
|
||||
};
|
||||
|
||||
shared::run_main(args.as_main_args(), &cmd_line, sandbox_info);
|
||||
0
|
||||
}
|
||||
@@ -7,6 +7,9 @@ publish = false
|
||||
default = ["accelerated_osr"]
|
||||
accelerated_osr = ["cef/accelerated_osr"]
|
||||
|
||||
[package.metadata.cef.bundle]
|
||||
helper_name = "cefsimple_helper"
|
||||
|
||||
[dependencies]
|
||||
cef.workspace = true
|
||||
wgpu.workspace = true
|
||||
|
||||
@@ -33,8 +33,10 @@ wrap_app! {
|
||||
command_line.append_switch(Some(&"hide-crash-restore-bubble".into()));
|
||||
command_line.append_switch(Some(&"use-mock-keychain".into()));
|
||||
command_line.append_switch(Some(&"enable-logging=stderr".into()));
|
||||
command_line
|
||||
.append_switch_with_value(Some(&"remote-debugging-port".into()), Some(&"9229".into()));
|
||||
command_line.append_switch_with_value(
|
||||
Some(&"remote-debugging-port".into()),
|
||||
Some(&"9229".into()),
|
||||
);
|
||||
}
|
||||
|
||||
fn browser_process_handler(&self) -> Option<cef::BrowserProcessHandler> {
|
||||
@@ -223,7 +225,9 @@ wrap_render_handler! {
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
@@ -348,7 +352,9 @@ wrap_render_handler! {
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
|
||||
34
examples/tests_shared/Cargo.toml
Normal file
34
examples/tests_shared/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "tests_shared"
|
||||
publish = false
|
||||
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
sandbox = ["cef/sandbox"]
|
||||
|
||||
[dependencies]
|
||||
cef.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
workspace = true
|
||||
features = [
|
||||
"Win32_System_Performance",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2.workspace = true
|
||||
objc2-app-kit = { workspace = true, features = ["NSApplication", "NSResponder"] }
|
||||
objc2-foundation.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
glib.workspace = true
|
||||
205
examples/tests_shared/resources/osr_test.html
Normal file
205
examples/tests_shared/resources/osr_test.html
Normal file
@@ -0,0 +1,205 @@
|
||||
<html>
|
||||
<head><title>OSR Test</title></head>
|
||||
<style>
|
||||
.red_hover:hover {color:red;}
|
||||
#li { width: 530px; }
|
||||
body {background:rgba(255, 0, 0, 0.5); }
|
||||
input {-webkit-appearance: none; }
|
||||
#LI11select {width: 75px;}
|
||||
#LI11select option { background-color: cyan; }
|
||||
.dropdiv {
|
||||
width:50px;
|
||||
height:50px;
|
||||
border:1px solid #aaaaaa;
|
||||
float: left;
|
||||
}
|
||||
#dragdiv {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: green;
|
||||
margin: 10px;
|
||||
}
|
||||
#draghere {
|
||||
position: relative;
|
||||
z-index: -1;
|
||||
top: 7px;
|
||||
left: 7px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
#touchdiv, #pointerdiv {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: red;
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function getElement(id) { return document.getElementById(id); }
|
||||
function makeH1Red() { getElement('LI00').style.color='red'; }
|
||||
function makeH1Black() { getElement('LI00').style.color='black'; }
|
||||
function navigate() { location.href='?k='+getElement('editbox').value; }
|
||||
function load() {
|
||||
var elems = [];
|
||||
var param = { type: 'ElementBounds', elems: elems };
|
||||
|
||||
elems.push(getElementBounds('LI00'));
|
||||
elems.push(getElementBounds('LI01'));
|
||||
elems.push(getElementBounds('LI02'));
|
||||
elems.push(getElementBounds('LI03'));
|
||||
elems.push(getElementBounds('LI04'));
|
||||
elems.push(getElementBounds('LI05'));
|
||||
elems.push(getElementBounds('LI06'));
|
||||
elems.push(getElementBounds('LI07'));
|
||||
elems.push(getElementBounds('LI08'));
|
||||
elems.push(getElementBounds('LI09'));
|
||||
elems.push(getElementBounds('LI10'));
|
||||
elems.push(getElementBounds('LI11'));
|
||||
elems.push(getElementBounds('LI11select'));
|
||||
elems.push(getElementBounds('email'));
|
||||
elems.push(getElementBounds('quickmenu'));
|
||||
elems.push(getElementBounds('editbox'));
|
||||
elems.push(getElementBounds('btnnavigate'));
|
||||
elems.push(getElementBounds('dropdiv'));
|
||||
elems.push(getElementBounds('dragdiv'));
|
||||
elems.push(getElementBounds('touchdiv'));
|
||||
elems.push(getElementBounds('pointerdiv'));
|
||||
|
||||
if (window.testQuery)
|
||||
window.testQuery({request: JSON.stringify(param)});
|
||||
|
||||
fillDropDown();
|
||||
}
|
||||
|
||||
function fillDropDown() {
|
||||
var select = document.getElementById('LI11select');
|
||||
for (var i = 1; i < 21; i++)
|
||||
select.options.add(new Option('Option ' + i, i));
|
||||
}
|
||||
|
||||
function getElementBounds(id) {
|
||||
var element = document.getElementById(id);
|
||||
var bounds = element.getBoundingClientRect();
|
||||
return {
|
||||
id: id,
|
||||
x: Math.floor(bounds.x),
|
||||
y: Math.floor(bounds.y),
|
||||
width: Math.floor(bounds.width),
|
||||
height: Math.floor(bounds.height)
|
||||
};
|
||||
}
|
||||
|
||||
function onEventTest(event) {
|
||||
var param = 'osr' + event.type;
|
||||
|
||||
if (event.type == "click")
|
||||
param += event.button;
|
||||
|
||||
// Results in a call to the OnQuery method in os_rendering_unittest.cc.
|
||||
if (window.testQuery)
|
||||
window.testQuery({request: param});
|
||||
}
|
||||
|
||||
function onFocusTest(ev) {
|
||||
if (window.testQuery)
|
||||
window.testQuery({request: "osrfocus" + ev.target.id});
|
||||
}
|
||||
|
||||
function allowDrop(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function drag(ev) {
|
||||
ev.dataTransfer.setData("Text",ev.target.id);
|
||||
}
|
||||
|
||||
function drop(ev) {
|
||||
var data=ev.dataTransfer.getData("Text");
|
||||
ev.target.innerHTML = '';
|
||||
var dragged = document.getElementById(data);
|
||||
dragged.setAttribute('draggable', 'false');
|
||||
ev.target.appendChild(dragged);
|
||||
if (window.testQuery)
|
||||
window.testQuery({request: "osrdrop"});
|
||||
}
|
||||
|
||||
function selectText(ev) {
|
||||
var element = ev.target;
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
function onTouchEvent(ev) {
|
||||
var param = 'osr' + ev.type;
|
||||
// For Touch start also include touch points.
|
||||
if (event.type == "touchstart")
|
||||
param += ev.touches.length;
|
||||
// For Touch Move include the touches that changed.
|
||||
if (event.type == "touchmove")
|
||||
param += ev.changedTouches.length;
|
||||
|
||||
// Results in a call to the OnQuery method in os_rendering_unittest.cc.
|
||||
if (window.testQuery)
|
||||
window.testQuery({request: param});
|
||||
}
|
||||
|
||||
function onPointerEvent(ev) {
|
||||
var param = 'osr' + ev.type + ' ' + ev.pointerType;
|
||||
if (window.testQuery)
|
||||
window.testQuery({request: param});
|
||||
}
|
||||
|
||||
</script>
|
||||
<body onfocus='onEventTest(event)' onblur='onEventTest(event)' onload='load();'>
|
||||
<h1 id='LI00' onclick="onEventTest(event)">
|
||||
OSR Testing h1 - Focus and blur
|
||||
<select id='LI11select'>
|
||||
<option value='0'>Default</option>
|
||||
</select>
|
||||
this page and will get this red black
|
||||
</h1>
|
||||
<ol>
|
||||
<li id='LI01'>OnPaint should be called each time a page loads</li>
|
||||
<li id='LI02' style='cursor:pointer;'><span>Move mouse
|
||||
to require an OnCursorChange call</span></li>
|
||||
<li id='LI03' onmousemove="onEventTest(event)"><span>Hover will color this with
|
||||
red. Will trigger OnPaint once on enter and once on leave</span></li>
|
||||
<li id='LI04'>Right clicking will show contextual menu and will request
|
||||
GetScreenPoint</li>
|
||||
<li id='LI05'>IsWindowRenderingDisabled should be true</li>
|
||||
<li id='LI06'>WasResized should trigger full repaint if size changes.
|
||||
</li>
|
||||
<li id='LI07'>Invalidate should trigger OnPaint once</li>
|
||||
<li id='LI08'>Click and write here with SendKeyEvent to trigger repaints:
|
||||
<input id='editbox' type='text' value='' size="5" onfocus="onFocusTest(event)"></li>
|
||||
<li id='LI09'>Click here with SendMouseClickEvent to navigate:
|
||||
<input id='btnnavigate' type='button' onclick='navigate()'
|
||||
value='Click here to navigate' /></li>
|
||||
<li id='LI10' title='EXPECTED_TOOLTIP'>Mouse over this element will
|
||||
trigger show a tooltip</li>
|
||||
<li id='LI11' onclick='selectText(event)'>SELECTED_TEXT_RANGE</li>
|
||||
<li><input id='email' type='text' size=10 inputmode='email' onfocus="onFocusTest(event)"></li>
|
||||
<li id="quickmenu">Long touch press should trigger quick menu</li>
|
||||
</ol>
|
||||
|
||||
<div class="dropdiv" id="dropdiv" ondrop="drop(event)" ondragover="allowDrop(event)">
|
||||
<span id="draghere">Drag here</span>
|
||||
</div>
|
||||
<div class="dropdiv">
|
||||
<div id="dragdiv" draggable="true" ondragstart="drag(event)"></div>
|
||||
</div>
|
||||
<div id="touchdiv" ontouchstart="onTouchEvent(event)" ontouchend="onTouchEvent(event)" ontouchmove="onTouchEvent(event)" ontouchcancel="onTouchEvent(event)">
|
||||
</div>
|
||||
<div id="pointerdiv" onpointerdown="onPointerEvent(event)" onpointerup="onPointerEvent(event)" onpointermove="onPointerEvent(event)" onpointercancel="onPointerEvent(event)">
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
</body>
|
||||
</html>
|
||||
9
examples/tests_shared/resources/pdf.html
Normal file
9
examples/tests_shared/resources/pdf.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>PDF Test</title>
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
<iframe src="pdf.pdf" width="500" height="500"></iframe>
|
||||
<iframe src="pdf.pdf" width="500" height="500"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
BIN
examples/tests_shared/resources/pdf.pdf
Normal file
BIN
examples/tests_shared/resources/pdf.pdf
Normal file
Binary file not shown.
BIN
examples/tests_shared/resources/window_icon.1x.png
Normal file
BIN
examples/tests_shared/resources/window_icon.1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 603 B |
BIN
examples/tests_shared/resources/window_icon.2x.png
Normal file
BIN
examples/tests_shared/resources/window_icon.2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 B |
262
examples/tests_shared/src/browser/client_app_browser.rs
Normal file
262
examples/tests_shared/src/browser/client_app_browser.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
use crate::{
|
||||
browser::main_message_loop_external_pump::*,
|
||||
common::{client_app::*, client_switches::*},
|
||||
};
|
||||
use cef::*;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub trait Delegate: Send {
|
||||
fn on_before_command_line_processing(
|
||||
&self,
|
||||
_app: &Arc<ClientAppBrowser>,
|
||||
_command_line: &mut CommandLine,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_register_custom_preferences(
|
||||
&self,
|
||||
_app: &Arc<ClientAppBrowser>,
|
||||
_type_: PreferencesType,
|
||||
_registrar: &mut PreferenceRegistrar,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_context_initialized(&self, _app: &Arc<ClientAppBrowser>) {}
|
||||
|
||||
fn on_already_running_app_relaunch(
|
||||
&self,
|
||||
_app: &Arc<ClientAppBrowser>,
|
||||
_command_line: &mut CommandLine,
|
||||
_current_directory: &Path,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn default_client(&self, _app: &Arc<ClientAppBrowser>) -> Option<Client> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientAppBrowser {
|
||||
delegates: Vec<Box<dyn Delegate>>,
|
||||
}
|
||||
|
||||
impl ClientAppBrowser {
|
||||
pub fn new(delegates: Vec<Box<dyn Delegate>>) -> Arc<Self> {
|
||||
Arc::new(Self { delegates })
|
||||
}
|
||||
|
||||
pub fn populate_settings(
|
||||
command_line: Option<CommandLine>,
|
||||
cookieable_schemes: Vec<String>,
|
||||
settings: Settings,
|
||||
) -> Settings {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
let settings = {
|
||||
Settings {
|
||||
multi_threaded_message_loop: command_line.as_ref().map_or(0, |command_line| {
|
||||
command_line.has_switch(Some(&CefString::from(MULTI_THREADED_MESSAGE_LOOP)))
|
||||
}),
|
||||
..settings
|
||||
}
|
||||
};
|
||||
|
||||
let settings = if settings.multi_threaded_message_loop == 0 {
|
||||
Settings {
|
||||
external_message_pump: command_line.as_ref().map_or(0, |command_line| {
|
||||
command_line.has_switch(Some(&CefString::from(EXTERNAL_MESSAGE_PUMP)))
|
||||
}),
|
||||
..settings
|
||||
}
|
||||
} else {
|
||||
settings
|
||||
};
|
||||
|
||||
let cookieable_schemes_list = CefString::from(cookieable_schemes.join(",").as_str());
|
||||
|
||||
Settings {
|
||||
cookieable_schemes_list,
|
||||
..settings
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delegates(&self) -> &[Box<dyn Delegate>] {
|
||||
&self.delegates
|
||||
}
|
||||
}
|
||||
|
||||
wrap_app! {
|
||||
pub struct ClientAppBrowserApp {
|
||||
base: ClientApp,
|
||||
client_app_browser: Arc<ClientAppBrowser>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn on_before_command_line_processing(
|
||||
&self,
|
||||
process_type: Option<&CefString>,
|
||||
command_line: Option<&mut CommandLine>,
|
||||
) {
|
||||
let (Some(process_type), Some(command_line)) = (process_type, command_line) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Pass additional command-line flags to the browser process.
|
||||
if process_type.to_string().is_empty() {
|
||||
// Pass additional command-line flags when off-screen rendering is enabled.
|
||||
if command_line.has_switch(Some(&CefString::from(OFF_SCREEN_RENDERING_ENABLED)))
|
||||
!= 0
|
||||
&& command_line.has_switch(Some(&CefString::from(SHARED_TEXTURE_ENABLED))) == 0
|
||||
{
|
||||
// Use software rendering and compositing (disable GPU) for increased FPS
|
||||
// and decreased CPU usage. This will also disable WebGL so remove these
|
||||
// switches if you need that capability.
|
||||
// See https://github.com/chromiumembedded/cef/issues/1257 for details.
|
||||
if command_line.has_switch(Some(&CefString::from(ENABLE_GPU))) == 0 {
|
||||
command_line.append_switch(Some(&CefString::from("disable-gpu")));
|
||||
command_line
|
||||
.append_switch(Some(&CefString::from("disable-gpu-compositing")));
|
||||
}
|
||||
}
|
||||
|
||||
if command_line.has_switch(Some(&CefString::from(USE_VIEWS))) != 0
|
||||
&& command_line.has_switch(Some(&CefString::from("top-chrome-md"))) == 0
|
||||
{
|
||||
// Use non-material mode on all platforms by default. Among other things
|
||||
// this causes menu buttons to show hover state. See usage of
|
||||
// MaterialDesignController::IsModeMaterial() in Chromium code.
|
||||
command_line.append_switch_with_value(
|
||||
Some(&CefString::from("top-chrome-md")),
|
||||
Some(&CefString::from("non-material")),
|
||||
);
|
||||
}
|
||||
|
||||
// Disable the toolchain prompt on macOS.
|
||||
#[cfg(target_os = "macos")]
|
||||
command_line.append_switch(Some(&CefString::from("use-mock-keychain")));
|
||||
|
||||
// On Linux, in off screen rendering (OSR) shared texture mode, we must
|
||||
// ensure that ANGLE uses the EGL backend. Without this, DMABUF based
|
||||
// rendering will fail. The Chromium fallback path uses X11 pixmaps,
|
||||
// which are only supported by Mesa drivers (e.g., AMD and Intel).
|
||||
//
|
||||
// While Mesa supports DMABUFs via both EGL and pixmaps, the EGL based
|
||||
// DMA BUF import path is more robust and required for compatibility with
|
||||
// drivers like NVIDIA that do not support pixmaps.
|
||||
//
|
||||
// We also append the kOzonePlatform switch with value x11 to ensure
|
||||
// that X11 semantics are preserved, which is necessary for compatibility
|
||||
// with some GDK/X11 integrations (e.g. Wayland with AMD).
|
||||
#[cfg(target_os = "linux")]
|
||||
if command_line.has_switch(Some(&CefString::from(OFF_SCREEN_RENDERING_ENABLED)))
|
||||
!= 0
|
||||
&& command_line.has_switch(Some(&CefString::from(SHARED_TEXTURE_ENABLED))) != 0
|
||||
{
|
||||
if command_line.has_switch(Some(&CefString::from(USE_ANGLE))) == 0 {
|
||||
command_line.append_switch_with_value(
|
||||
Some(&CefString::from(USE_ANGLE)),
|
||||
Some(&CefString::from("gl-egl")),
|
||||
);
|
||||
}
|
||||
if command_line.has_switch(Some(&CefString::from(OZONE_PLATFORM))) == 0 {
|
||||
command_line.append_switch_with_value(
|
||||
Some(&CefString::from(OZONE_PLATFORM)),
|
||||
Some(&CefString::from("X11")),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for delegate in self.client_app_browser.delegates() {
|
||||
delegate.on_before_command_line_processing(&self.client_app_browser, command_line);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
self.base.on_register_custom_schemes(registrar);
|
||||
}
|
||||
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(ClientAppBrowserProcessHandler::new(
|
||||
self.client_app_browser.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_browser_process_handler! {
|
||||
pub struct ClientAppBrowserProcessHandler {
|
||||
client_app_browser: Arc<ClientAppBrowser>,
|
||||
}
|
||||
|
||||
impl BrowserProcessHandler {
|
||||
fn on_register_custom_preferences(
|
||||
&self,
|
||||
type_: PreferencesType,
|
||||
registrar: Option<&mut PreferenceRegistrar>,
|
||||
) {
|
||||
let Some(registrar) = registrar else {
|
||||
return;
|
||||
};
|
||||
|
||||
for delegate in self.client_app_browser.delegates() {
|
||||
delegate.on_register_custom_preferences(&self.client_app_browser, type_, registrar);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_context_initialized(&self) {
|
||||
for delegate in self.client_app_browser.delegates() {
|
||||
delegate.on_context_initialized(&self.client_app_browser);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_already_running_app_relaunch(
|
||||
&self,
|
||||
command_line: Option<&mut CommandLine>,
|
||||
current_directory: Option<&CefString>,
|
||||
) -> i32 {
|
||||
let (Some(command_line), Some(current_directory)) = (command_line, current_directory)
|
||||
else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let delegates = self.client_app_browser.delegates();
|
||||
if !delegates.is_empty() {
|
||||
let current_directory = PathBuf::from(current_directory.to_string().as_str());
|
||||
|
||||
for delegate in delegates {
|
||||
if delegate.on_already_running_app_relaunch(
|
||||
&self.client_app_browser,
|
||||
command_line,
|
||||
current_directory.as_path(),
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn on_schedule_message_pump_work(&self, delay_ms: i64) {
|
||||
if let Some(message_loop) = get_main_message_loop() {
|
||||
if let Ok(mut message_loop) = message_loop.lock() {
|
||||
message_loop.on_schedule_message_pump_work(delay_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_client(&self) -> Option<Client> {
|
||||
for delegate in self.client_app_browser.delegates() {
|
||||
let client = delegate.default_client(&self.client_app_browser);
|
||||
if client.is_some() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
81
examples/tests_shared/src/browser/file_util.rs
Normal file
81
examples/tests_shared/src/browser/file_util.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use cef::*;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
fn allow_file_io() -> bool {
|
||||
currently_on(ThreadId::UI) == 0 && currently_on(ThreadId::IO) == 0
|
||||
}
|
||||
|
||||
/// Reads the file at `path` into `contents` and returns true on success and
|
||||
/// false on error. In case of I/O error, `contents` holds the data that could
|
||||
/// be read from the file before the error occurred. When the file size exceeds
|
||||
/// `max_size`, the function returns false with `contents` holding the file
|
||||
/// truncated to `max_size`. `contents` may be [None], in which case this
|
||||
/// function is useful for its side effect of priming the disk cache (could be
|
||||
/// used for unit tests). Calling this function on the browser process UI or IO
|
||||
/// threads is not allowed.
|
||||
pub fn read_file_to_buffer<P: AsRef<Path>>(
|
||||
path: P,
|
||||
contents: &mut Option<Vec<u8>>,
|
||||
max_size: usize,
|
||||
) -> bool {
|
||||
if !allow_file_io() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(contents) = contents.as_mut() {
|
||||
contents.clear();
|
||||
contents.reserve(max_size);
|
||||
}
|
||||
let Ok(mut file) = File::open(path) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Many files supplied in `path` have incorrect size (proc files etc).
|
||||
// Hence, the file is read sequentially as opposed to a one-shot read.
|
||||
const BUFFER_SIZE: usize = 1 << 16;
|
||||
let mut buffer = vec![0; BUFFER_SIZE];
|
||||
let mut size = 0;
|
||||
|
||||
while let Ok(read) = file.read(&mut buffer) {
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(contents) = contents.as_mut() {
|
||||
contents.extend_from_slice(&buffer[..read.min(max_size - size)]);
|
||||
}
|
||||
|
||||
size += read;
|
||||
if size > max_size {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Writes the given buffer into the file, overwriting any data that was
|
||||
/// previously there. Returns the number of bytes written, or [None] on error.
|
||||
/// Calling this function on the browser process UI or IO threads is not allowed.
|
||||
pub fn write_file<P: AsRef<Path>>(path: P, contents: &[u8]) -> Option<usize> {
|
||||
if !allow_file_io() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut file = File::create(path).ok()?;
|
||||
let mut size = 0;
|
||||
|
||||
while size < contents.len() {
|
||||
let write = file.write(&contents[size..]).unwrap_or(0);
|
||||
if write == 0 {
|
||||
break;
|
||||
}
|
||||
size += write;
|
||||
}
|
||||
|
||||
Some(size)
|
||||
}
|
||||
56
examples/tests_shared/src/browser/geometry_util.rs
Normal file
56
examples/tests_shared/src/browser/geometry_util.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use cef::*;
|
||||
|
||||
pub const fn logical_value_to_device(value: i32, scale_factor: f32) -> i32 {
|
||||
(value as f32 * scale_factor) as i32
|
||||
}
|
||||
|
||||
pub const fn logical_rect_to_device(rect: Rect, scale_factor: f32) -> Rect {
|
||||
Rect {
|
||||
x: logical_value_to_device(rect.x, scale_factor),
|
||||
y: logical_value_to_device(rect.y, scale_factor),
|
||||
width: logical_value_to_device(rect.width, scale_factor),
|
||||
height: logical_value_to_device(rect.height, scale_factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn device_value_to_logical(value: i32, scale_factor: f32) -> i32 {
|
||||
(value as f32 / scale_factor) as i32
|
||||
}
|
||||
|
||||
pub const fn device_rect_to_logical(rect: Rect, scale_factor: f32) -> Rect {
|
||||
Rect {
|
||||
x: device_value_to_logical(rect.x, scale_factor),
|
||||
y: device_value_to_logical(rect.y, scale_factor),
|
||||
width: device_value_to_logical(rect.width, scale_factor),
|
||||
height: device_value_to_logical(rect.height, scale_factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn device_mouse_event_to_logical(event: MouseEvent, scale_factor: f32) -> MouseEvent {
|
||||
MouseEvent {
|
||||
x: device_value_to_logical(event.x, scale_factor),
|
||||
y: device_value_to_logical(event.y, scale_factor),
|
||||
..event
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn device_touch_event_to_logical(event: TouchEvent, scale_factor: f32) -> TouchEvent {
|
||||
TouchEvent {
|
||||
x: event.x / scale_factor,
|
||||
y: event.y / scale_factor,
|
||||
..event
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_window_bounds(display: &Rect, window: &mut Rect) {
|
||||
window.x = window.x.max(display.x);
|
||||
window.y = window.y.max(display.y);
|
||||
window.width = window.width.clamp(100, display.width);
|
||||
window.height = window.height.clamp(100, display.height);
|
||||
if window.x + window.width > display.x + display.width {
|
||||
window.x = display.x + display.width - window.width;
|
||||
}
|
||||
if window.y + window.height > display.y + display.height {
|
||||
window.y = display.y + display.height - window.height;
|
||||
}
|
||||
}
|
||||
135
examples/tests_shared/src/browser/main_message_loop.rs
Normal file
135
examples/tests_shared/src/browser/main_message_loop.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use cef::*;
|
||||
use std::{
|
||||
mem,
|
||||
sync::{Arc, Mutex, OnceLock},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::Foundation::HWND;
|
||||
|
||||
static INSTANCE: OnceLock<Arc<Mutex<Option<Box<dyn MainMessageLoop>>>>> = OnceLock::new();
|
||||
|
||||
pub fn get_main_message_loop() -> Arc<Mutex<Option<Box<dyn MainMessageLoop>>>> {
|
||||
INSTANCE.get_or_init(|| Arc::new(Mutex::new(None))).clone()
|
||||
}
|
||||
|
||||
pub fn set_main_message_loop(
|
||||
mut main_message_loop: Option<Box<dyn MainMessageLoop>>,
|
||||
) -> Option<Box<dyn MainMessageLoop>> {
|
||||
let instance = get_main_message_loop();
|
||||
let Ok(mut instance) = instance.lock() else {
|
||||
return main_message_loop;
|
||||
};
|
||||
mem::swap(&mut *instance, &mut main_message_loop);
|
||||
main_message_loop
|
||||
}
|
||||
|
||||
pub fn currently_on_main_thread() -> bool {
|
||||
let instance = get_main_message_loop();
|
||||
let Ok(instance) = instance.lock() else {
|
||||
return false;
|
||||
};
|
||||
let Some(instance) = instance.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
instance.run_tasks_on_current_thread()
|
||||
}
|
||||
|
||||
pub fn main_post_task(task: Option<&mut Task>) {
|
||||
let instance = get_main_message_loop();
|
||||
let Ok(mut instance) = instance.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(instance) = instance.as_mut() else {
|
||||
return;
|
||||
};
|
||||
instance.post_task(task);
|
||||
}
|
||||
|
||||
pub fn main_post_once(closure: Box<dyn Send + FnOnce()>) {
|
||||
let instance = get_main_message_loop();
|
||||
let Ok(mut instance) = instance.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(instance) = instance.as_mut() else {
|
||||
return;
|
||||
};
|
||||
instance.post_once(closure);
|
||||
}
|
||||
|
||||
pub fn main_post_repeating(closure: Box<dyn Send + FnMut()>) {
|
||||
let instance = get_main_message_loop();
|
||||
let Ok(mut instance) = instance.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(instance) = instance.as_mut() else {
|
||||
return;
|
||||
};
|
||||
instance.post_repeating(closure);
|
||||
}
|
||||
|
||||
wrap_task! {
|
||||
struct OnceClosure {
|
||||
closure: Arc<Mutex<Option<Box<dyn Send + FnOnce()>>>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn execute(&self) {
|
||||
let Ok(mut closure) = self.closure.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(closure) = closure.take() else {
|
||||
return;
|
||||
};
|
||||
closure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_task! {
|
||||
struct RepeatingClosure {
|
||||
closure: Arc<Mutex<Option<Box<dyn Send + FnMut()>>>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn execute(&self) {
|
||||
let Ok(mut closure) = self.closure.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(closure) = closure.as_mut() else {
|
||||
return;
|
||||
};
|
||||
closure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MainMessageLoop: Send {
|
||||
/// Run the message loop. The thread that this method is called on will be considered the main
|
||||
/// thread. This blocks until [MainMessageLoop::quit] is called.
|
||||
fn run(&mut self) -> i32;
|
||||
|
||||
/// Quit the message loop.
|
||||
fn quit(&mut self);
|
||||
|
||||
/// Post a task for execution on the main message loop.
|
||||
fn post_task(&mut self, task: Option<&mut Task>);
|
||||
|
||||
/// Returns true if this message loop runs tasks on the current thread.
|
||||
fn run_tasks_on_current_thread(&self) -> bool;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn set_current_modeless_dialog(&mut self, hwnd: HWND);
|
||||
|
||||
/// Post a closure for execution on the main message loop.
|
||||
fn post_once(&mut self, closure: Box<dyn Send + FnOnce()>) {
|
||||
let mut task = OnceClosure::new(Arc::new(Mutex::new(Some(closure))));
|
||||
self.post_task(Some(&mut task));
|
||||
}
|
||||
|
||||
/// Post a closure for execution on the main message loop.
|
||||
fn post_repeating(&mut self, closure: Box<dyn Send + FnMut()>) {
|
||||
let mut task = RepeatingClosure::new(Arc::new(Mutex::new(Some(closure))));
|
||||
self.post_task(Some(&mut task));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
use super::*;
|
||||
use glib::*;
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
os::fd::AsRawFd,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
thread,
|
||||
};
|
||||
|
||||
/// Return a timeout suitable for the glib loop, -1 to block forever,
|
||||
/// 0 to return right away, or a timeout in milliseconds from now.
|
||||
fn get_time_interval_milliseconds(cef_time: &cef::Time) -> i32 {
|
||||
let mut time = 0.0;
|
||||
time_to_doublet(Some(cef_time), Some(&mut time));
|
||||
if time == 0.0 {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let mut cef_now = Default::default();
|
||||
time_now(Some(&mut cef_now));
|
||||
let mut now = 0.0;
|
||||
time_to_doublet(Some(&cef_now), Some(&mut now));
|
||||
|
||||
// Be careful here. CefTime has a precision of microseconds, but we want a
|
||||
// value in milliseconds. If there are 5.5ms left, should the delay be 5 or
|
||||
// 6? It should be 6 to avoid executing delayed work too early.
|
||||
let interval = (time - now).ceil() * 1000.0;
|
||||
let interval = interval as i32;
|
||||
|
||||
// If this value is negative, then we need to run delayed work soon.
|
||||
interval.max(0)
|
||||
}
|
||||
|
||||
fn handle_eintr<T>(mut callback: impl FnMut() -> io::Result<T>) -> io::Result<T> {
|
||||
loop {
|
||||
match callback() {
|
||||
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
|
||||
result => break result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MainMessageLoopExternalPumpInner {
|
||||
should_quit: bool,
|
||||
main_context: MainContext,
|
||||
work_source: Source,
|
||||
timer_source: Option<Source>,
|
||||
delayed_work_time: Arc<Mutex<Option<cef::Time>>>,
|
||||
wakeup_pipe_write: io::PipeWriter,
|
||||
}
|
||||
|
||||
impl Drop for MainMessageLoopExternalPumpInner {
|
||||
fn drop(&mut self) {
|
||||
self.work_source.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl MainMessageLoopExternalPumpInner {
|
||||
pub fn new(pump: &Weak<Mutex<MainMessageLoopExternalPump>>) -> Self {
|
||||
let (mut wakeup_pipe_read, wakeup_pipe_write) =
|
||||
io::pipe().expect("Failed to create wakeup pipe");
|
||||
let delayed_work_time = Arc::new(Mutex::new(None));
|
||||
let main_context = MainContext::default();
|
||||
let work_source = {
|
||||
let pump = pump.clone();
|
||||
let delayed_work_time = delayed_work_time.clone();
|
||||
unix_fd_source_new(
|
||||
wakeup_pipe_read.as_raw_fd(),
|
||||
IOCondition::IN,
|
||||
None,
|
||||
Priority::DEFAULT_IDLE,
|
||||
move |raw_fd, condition| {
|
||||
let Some(pump) = pump.upgrade() else {
|
||||
return ControlFlow::Break;
|
||||
};
|
||||
let Ok(mut pump) = pump.lock() else {
|
||||
return ControlFlow::Break;
|
||||
};
|
||||
|
||||
// We usually have a single message on the wakeup pipe, since we are only
|
||||
// signaled when the queue went from empty to non-empty, but there can be
|
||||
// two messages if a task posted a task, hence we read at most two bytes.
|
||||
// The glib poll will tell us whether there was data, so this read shouldn't
|
||||
// block.
|
||||
if condition.contains(IOCondition::IN) {
|
||||
assert_eq!(wakeup_pipe_read.as_raw_fd(), raw_fd);
|
||||
|
||||
let mut buffer = [0; 16];
|
||||
let size = handle_eintr(|| wakeup_pipe_read.read(&mut buffer))
|
||||
.expect("Error reading from the wakeup pipe.");
|
||||
|
||||
match size {
|
||||
16 => {
|
||||
let mut delay_ms = [0; 8];
|
||||
delay_ms.copy_from_slice(&buffer[..8]);
|
||||
pump.on_schedule_work(i64::from_ne_bytes(delay_ms));
|
||||
delay_ms.copy_from_slice(&buffer[8..]);
|
||||
pump.on_schedule_work(i64::from_ne_bytes(delay_ms));
|
||||
}
|
||||
8..16 => {
|
||||
let mut delay_ms = [0; 8];
|
||||
delay_ms.copy_from_slice(&buffer[..8]);
|
||||
pump.on_schedule_work(i64::from_ne_bytes(delay_ms));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(delayed_work_time) = delayed_work_time.lock() {
|
||||
let delay = delayed_work_time
|
||||
.as_ref()
|
||||
.map(get_time_interval_milliseconds)
|
||||
.unwrap_or_default();
|
||||
|
||||
if delay == 0 {
|
||||
// The timer has expired. That condition will stay true until we process
|
||||
// that delayed work, so we don't need to record this differently.
|
||||
pump.on_timer_timeout();
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue
|
||||
},
|
||||
)
|
||||
};
|
||||
work_source.attach(Some(&main_context));
|
||||
|
||||
Self {
|
||||
should_quit: false,
|
||||
main_context,
|
||||
work_source,
|
||||
timer_source: None,
|
||||
delayed_work_time,
|
||||
wakeup_pipe_write,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_run(&mut self) -> bool {
|
||||
// We really only do a single task for each iteration of the loop. If we
|
||||
// have done something, assume there is likely something more to do. This
|
||||
// will mean that we don't block on the message pump until there was nothing
|
||||
// more to do. We also set this to true to make sure not to block on the
|
||||
// first iteration of the loop.
|
||||
let mut more_work_is_plausible = true;
|
||||
|
||||
// We run our own loop instead of using g_main_loop_quit in one of the
|
||||
// callbacks. This is so we only quit our own loops, and we don't quit
|
||||
// nested loops run by others.
|
||||
loop {
|
||||
// Don't block if we think we have more work to do.
|
||||
let block = !more_work_is_plausible;
|
||||
|
||||
more_work_is_plausible = self.main_context.iteration(block);
|
||||
if self.should_quit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to run the message pump until it is idle. However we don't have
|
||||
// that information here so we run the message loop "for a while".
|
||||
for _ in 0..10 {
|
||||
// Do some work.
|
||||
do_message_loop_work();
|
||||
|
||||
// Sleep to allow the CEF proc to do work.
|
||||
thread::sleep(Duration::from_micros(50000));
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn on_quit(&mut self) {
|
||||
self.should_quit = true;
|
||||
}
|
||||
|
||||
pub fn on_schedule_message_pump_work(&mut self, delay: i64) {
|
||||
let buffer = delay.to_ne_bytes();
|
||||
let size = handle_eintr(|| self.wakeup_pipe_write.write(&buffer)).unwrap_or_default();
|
||||
assert_eq!(
|
||||
size, 8,
|
||||
"Could not write to the UI message loop wakeup pipe!"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_timer(&mut self, delay: i64) {
|
||||
assert!(delay > 0);
|
||||
|
||||
let mut delayed_work_time = self
|
||||
.delayed_work_time
|
||||
.lock()
|
||||
.expect("Failed to lock delayed_work_time member");
|
||||
|
||||
let mut cef_now = Default::default();
|
||||
time_now(Some(&mut cef_now));
|
||||
let mut now = 0.0;
|
||||
time_to_doublet(Some(&cef_now), Some(&mut now));
|
||||
|
||||
let time = now + delay as f64 / 1000.0;
|
||||
let mut cef_time = Default::default();
|
||||
if time_from_doublet(time, Some(&mut cef_time)) == 0 {
|
||||
panic!("Failed to convert time to CEF time");
|
||||
}
|
||||
|
||||
*delayed_work_time = Some(cef_time);
|
||||
|
||||
if let Some(timer_source) = self.timer_source.take() {
|
||||
self.work_source.remove_child_source(&timer_source);
|
||||
}
|
||||
|
||||
let timer_source = timeout_source_new(
|
||||
Duration::from_millis(delay.max(0).unsigned_abs()),
|
||||
None,
|
||||
Priority::DEFAULT_IDLE,
|
||||
|| ControlFlow::Continue,
|
||||
);
|
||||
self.work_source.add_child_source(&timer_source);
|
||||
}
|
||||
|
||||
pub fn kill_timer(&mut self) {
|
||||
let mut delayed_work_time = self
|
||||
.delayed_work_time
|
||||
.lock()
|
||||
.expect("Failed to lock delayed_work_time member");
|
||||
|
||||
*delayed_work_time = None;
|
||||
|
||||
if let Some(timer_source) = self.timer_source.take() {
|
||||
self.work_source.remove_child_source(&timer_source);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_timer_pending(&self) -> bool {
|
||||
let delayed_work_time = self
|
||||
.delayed_work_time
|
||||
.lock()
|
||||
.expect("Failed to lock delayed_work_time member");
|
||||
let delay = delayed_work_time
|
||||
.as_ref()
|
||||
.map(get_time_interval_milliseconds)
|
||||
.unwrap_or_default();
|
||||
delay > 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
use super::*;
|
||||
use objc2::{define_class, msg_send, rc::Retained, sel, AnyThread, DefinedClass};
|
||||
use objc2_app_kit::{NSApp, NSEventTrackingRunLoopMode};
|
||||
use objc2_foundation::{
|
||||
MainThreadMarker, NSNumber, NSObject, NSObjectNSThreadPerformAdditions, NSObjectProtocol,
|
||||
NSRunLoop, NSRunLoopCommonModes, NSThread, NSTimer,
|
||||
};
|
||||
use std::sync::{Mutex, Weak};
|
||||
|
||||
define_class! {
|
||||
#[unsafe(super(NSObject))]
|
||||
#[ivars = Weak<Mutex<MainMessageLoopExternalPump>>]
|
||||
struct EventHandler;
|
||||
|
||||
impl EventHandler {
|
||||
#[unsafe(method(scheduleWork:))]
|
||||
fn schedule_work(&self, delay_ms: &NSNumber) {
|
||||
let Ok(delay_ms) = i64::try_from(delay_ms.integerValue()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pump) = self.ivars().upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Ok(mut pump) = pump.lock() else {
|
||||
return;
|
||||
};
|
||||
|
||||
pump.on_schedule_work(delay_ms);
|
||||
}
|
||||
|
||||
#[unsafe(method(timerTimeout:))]
|
||||
fn timer_timeout(&self, _: &NSTimer) {
|
||||
let Some(pump) = self.ivars().upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Ok(mut pump) = pump.lock() else {
|
||||
return;
|
||||
};
|
||||
|
||||
pump.on_timer_timeout();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for EventHandler {}
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
fn new(pump: Weak<Mutex<MainMessageLoopExternalPump>>) -> Retained<Self> {
|
||||
let this = Self::alloc().set_ivars(pump);
|
||||
unsafe { msg_send![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MainMessageLoopExternalPumpInner {
|
||||
owner_thread: Retained<NSThread>,
|
||||
timer: Option<Retained<NSTimer>>,
|
||||
event_handler: Retained<EventHandler>,
|
||||
}
|
||||
|
||||
unsafe impl Send for MainMessageLoopExternalPumpInner {}
|
||||
|
||||
impl MainMessageLoopExternalPumpInner {
|
||||
pub fn new(pump: &Weak<Mutex<MainMessageLoopExternalPump>>) -> Self {
|
||||
let event_handler = EventHandler::new(pump.clone());
|
||||
Self {
|
||||
owner_thread: NSThread::currentThread(),
|
||||
timer: None,
|
||||
event_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_run(&mut self) -> bool {
|
||||
let Some(mtm) = MainThreadMarker::new() else {
|
||||
return false;
|
||||
};
|
||||
NSApp(mtm).run();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn on_quit(&mut self) {
|
||||
let Some(mtm) = MainThreadMarker::new() else {
|
||||
return;
|
||||
};
|
||||
NSApp(mtm).stop(None);
|
||||
}
|
||||
|
||||
pub fn on_schedule_message_pump_work(&mut self, delay: i64) {
|
||||
// This method may be called on any thread.
|
||||
let delay = isize::try_from(delay).unwrap_or(isize::MAX);
|
||||
let number = NSNumber::numberWithInteger(delay);
|
||||
unsafe {
|
||||
self.event_handler
|
||||
.performSelector_onThread_withObject_waitUntilDone(
|
||||
sel!(scheduleWork:),
|
||||
&self.owner_thread,
|
||||
Some(&number),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_timer(&mut self, delay: i64) {
|
||||
let delay_s = delay as f64 / 1000.0;
|
||||
let timer = unsafe {
|
||||
NSTimer::timerWithTimeInterval_target_selector_userInfo_repeats(
|
||||
delay_s,
|
||||
&self.event_handler,
|
||||
sel!(timerTimeout:),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
// Add the timer to default and tracking runloop modes.
|
||||
let owner_runloop = NSRunLoop::currentRunLoop();
|
||||
unsafe {
|
||||
owner_runloop.addTimer_forMode(&timer, NSRunLoopCommonModes);
|
||||
owner_runloop.addTimer_forMode(&timer, NSEventTrackingRunLoopMode);
|
||||
}
|
||||
|
||||
self.timer = Some(timer);
|
||||
}
|
||||
|
||||
pub fn kill_timer(&mut self) {
|
||||
let Some(timer) = self.timer.take() else {
|
||||
return;
|
||||
};
|
||||
timer.invalidate();
|
||||
}
|
||||
|
||||
pub fn is_timer_pending(&self) -> bool {
|
||||
self.timer.is_some()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
use super::{main_message_loop::*, main_message_loop_std::*};
|
||||
use cef::*;
|
||||
use std::{
|
||||
sync::{Arc, Mutex, OnceLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::Foundation::HWND;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod win;
|
||||
#[cfg(target_os = "windows")]
|
||||
use win::MainMessageLoopExternalPumpInner;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac;
|
||||
#[cfg(target_os = "macos")]
|
||||
use mac::MainMessageLoopExternalPumpInner;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
use linux::MainMessageLoopExternalPumpInner;
|
||||
|
||||
/// Special timer delay placeholder value. Intentionally 32-bit for Windows and
|
||||
/// OS X platform API compatibility.
|
||||
const TIMER_DELAY_PLACEHOLDER: i32 = i32::MAX;
|
||||
|
||||
// The maximum number of milliseconds we're willing to wait between calls to
|
||||
// [MainMessageLoopExternalPump::do_work].
|
||||
const MAX_TIMER_DELAY: i64 = 1000 / 30; // 30fps
|
||||
|
||||
static INSTANCE: OnceLock<Weak<Mutex<MainMessageLoopExternalPump>>> = OnceLock::new();
|
||||
|
||||
pub fn get_main_message_loop() -> Option<Arc<Mutex<MainMessageLoopExternalPump>>> {
|
||||
INSTANCE.get()?.upgrade()
|
||||
}
|
||||
|
||||
pub fn set_main_message_loop(
|
||||
main_message_loop: Option<Arc<Mutex<MainMessageLoopExternalPump>>>,
|
||||
) -> Option<Arc<Mutex<MainMessageLoopExternalPump>>> {
|
||||
let main_message_loop = main_message_loop
|
||||
.as_ref()
|
||||
.map(Arc::downgrade)
|
||||
.unwrap_or_default();
|
||||
if let Err(instance) = INSTANCE.set(main_message_loop) {
|
||||
instance.upgrade()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MainMessageLoopExternalPump {
|
||||
standard_message_loop: Arc<Mutex<Option<Box<dyn MainMessageLoop>>>>,
|
||||
is_active: bool,
|
||||
reentrancy_detected: bool,
|
||||
inner: MainMessageLoopExternalPumpInner,
|
||||
}
|
||||
|
||||
impl MainMessageLoopExternalPump {
|
||||
pub fn new() -> Arc<Mutex<Self>> {
|
||||
let external_pump = Arc::new_cyclic(|weak_ref| {
|
||||
Mutex::new(Self {
|
||||
standard_message_loop: MainMessageLoopStd::new(),
|
||||
is_active: false,
|
||||
reentrancy_detected: false,
|
||||
inner: MainMessageLoopExternalPumpInner::new(weak_ref),
|
||||
})
|
||||
});
|
||||
set_main_message_loop(Some(external_pump.clone()));
|
||||
external_pump
|
||||
}
|
||||
|
||||
/// Called from [BrowserProcessHandler::on_schedule_message_pump_work] on any thread.
|
||||
/// The platform subclass must implement this method and schedule a call to
|
||||
/// [MainMessageLoopExternalPump::on_schedule_work] on the main application thread.
|
||||
pub fn on_schedule_message_pump_work(&mut self, delay: i64) {
|
||||
self.inner.on_schedule_message_pump_work(delay);
|
||||
}
|
||||
|
||||
fn on_schedule_work(&mut self, delay: i64) {
|
||||
assert!(currently_on_main_thread());
|
||||
|
||||
if delay == i64::from(TIMER_DELAY_PLACEHOLDER) && self.is_timer_pending() {
|
||||
// Don't set the maximum timer requested from DoWork() if a timer event is
|
||||
// currently pending.
|
||||
return;
|
||||
}
|
||||
|
||||
self.kill_timer();
|
||||
|
||||
let delay = if delay <= 0 {
|
||||
// Execute the work immediately.
|
||||
self.do_work();
|
||||
0
|
||||
} else {
|
||||
// Never wait longer than the maximum allowed time.
|
||||
delay.min(MAX_TIMER_DELAY)
|
||||
};
|
||||
|
||||
// Results in call to on_timer_timeout after the specified delay.
|
||||
self.set_timer(delay);
|
||||
}
|
||||
|
||||
fn on_timer_timeout(&mut self) {
|
||||
assert!(currently_on_main_thread());
|
||||
self.kill_timer();
|
||||
self.do_work();
|
||||
}
|
||||
|
||||
/// Control the pending work timer in the platform subclass. Only called on
|
||||
/// the main application thread.
|
||||
fn set_timer(&mut self, delay: i64) {
|
||||
assert!(!self.is_timer_pending());
|
||||
assert!(delay > 0);
|
||||
self.inner.set_timer(delay);
|
||||
}
|
||||
|
||||
/// Control the pending work timer in the platform subclass. Only called on
|
||||
/// the main application thread.
|
||||
fn kill_timer(&mut self) {
|
||||
self.inner.kill_timer();
|
||||
}
|
||||
|
||||
/// Control the pending work timer in the platform subclass. Only called on
|
||||
/// the main application thread.
|
||||
fn is_timer_pending(&self) -> bool {
|
||||
self.inner.is_timer_pending()
|
||||
}
|
||||
|
||||
/// Handle work processing.
|
||||
fn do_work(&mut self) {
|
||||
let was_reentrant = self.perform_message_loop_work();
|
||||
if was_reentrant {
|
||||
self.on_schedule_message_pump_work(0);
|
||||
} else if !self.is_timer_pending() {
|
||||
self.on_schedule_message_pump_work(i64::from(TIMER_DELAY_PLACEHOLDER));
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_message_loop_work(&mut self) -> bool {
|
||||
if self.is_active {
|
||||
// When do_message_loop_work is called there may be various callbacks
|
||||
// (such as paint and IPC messages) that result in additional calls to this
|
||||
// method. If re-entrancy is detected we must repost a request again to the
|
||||
// owner thread to ensure that the discarded call is executed in the future.
|
||||
self.reentrancy_detected = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.reentrancy_detected = false;
|
||||
|
||||
self.is_active = true;
|
||||
do_message_loop_work();
|
||||
self.is_active = false;
|
||||
|
||||
// `reentrancy_detected` may have changed due to re-entrant calls to this
|
||||
// method.
|
||||
self.reentrancy_detected
|
||||
}
|
||||
}
|
||||
|
||||
impl MainMessageLoop for MainMessageLoopExternalPump {
|
||||
fn run(&mut self) -> i32 {
|
||||
if !self.inner.on_run() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self.kill_timer();
|
||||
|
||||
// We need to run the message pump until it is idle. However we don't have
|
||||
// that information here so we run the message loop "for a while".
|
||||
for _ in 0..10 {
|
||||
// Do some work.
|
||||
do_message_loop_work();
|
||||
|
||||
// Sleep to allow the CEF proc to do work.
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn quit(&mut self) {
|
||||
self.inner.on_quit();
|
||||
}
|
||||
|
||||
fn post_task(&mut self, task: Option<&mut Task>) {
|
||||
let Ok(mut standard_message_loop) = self.standard_message_loop.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(standard_message_loop) = standard_message_loop.as_mut() else {
|
||||
return;
|
||||
};
|
||||
standard_message_loop.post_task(task);
|
||||
}
|
||||
|
||||
fn run_tasks_on_current_thread(&self) -> bool {
|
||||
let Ok(standard_message_loop) = self.standard_message_loop.lock() else {
|
||||
return false;
|
||||
};
|
||||
let Some(standard_message_loop) = standard_message_loop.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
standard_message_loop.run_tasks_on_current_thread()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn set_current_modeless_dialog(&mut self, hwnd: HWND) {
|
||||
let Ok(mut standard_message_loop) = self.standard_message_loop.lock() else {
|
||||
return;
|
||||
};
|
||||
let Some(standard_message_loop) = standard_message_loop.as_mut() else {
|
||||
return;
|
||||
};
|
||||
standard_message_loop.set_current_modeless_dialog(hwnd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use super::*;
|
||||
use crate::browser::util_win::*;
|
||||
use std::{
|
||||
mem, ptr,
|
||||
sync::{Mutex, Weak},
|
||||
};
|
||||
use windows_sys::{w, Win32::Foundation::*, Win32::UI::WindowsAndMessaging::*};
|
||||
|
||||
const MSG_HAVE_WORK: u32 = WM_USER + 1;
|
||||
|
||||
pub struct MainMessageLoopExternalPumpInner {
|
||||
timer_pending: bool,
|
||||
main_thread_target: Option<usize>,
|
||||
}
|
||||
|
||||
impl MainMessageLoopExternalPumpInner {
|
||||
pub fn new(pump: &Weak<Mutex<MainMessageLoopExternalPump>>) -> Self {
|
||||
let main_thread_target = {
|
||||
const WINDOW_CLASS_NAME: *const u16 = w!("MainMessageLoopExternalPump");
|
||||
|
||||
let instance = get_code_module_handle();
|
||||
unsafe {
|
||||
let wcex = WNDCLASSEXW {
|
||||
cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
|
||||
lpfnWndProc: Some(Self::window_proc),
|
||||
hInstance: instance,
|
||||
lpszClassName: WINDOW_CLASS_NAME,
|
||||
..mem::zeroed()
|
||||
};
|
||||
RegisterClassExW(&wcex);
|
||||
|
||||
// Create the message handling window.
|
||||
let main_thread_target = CreateWindowExW(
|
||||
0,
|
||||
WINDOW_CLASS_NAME,
|
||||
ptr::null(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
HWND_MESSAGE,
|
||||
ptr::null_mut(),
|
||||
instance,
|
||||
ptr::null(),
|
||||
);
|
||||
assert!(!main_thread_target.is_null());
|
||||
main_thread_target
|
||||
}
|
||||
};
|
||||
|
||||
set_user_data(main_thread_target, Some(pump.clone()));
|
||||
|
||||
Self {
|
||||
timer_pending: false,
|
||||
main_thread_target: Some(main_thread_target as usize),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn window_proc(
|
||||
hwnd: HWND,
|
||||
message: u32,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
match message {
|
||||
MSG_HAVE_WORK | WM_TIMER => {
|
||||
if let Some(message_loop) =
|
||||
get_user_data::<Weak<Mutex<MainMessageLoopExternalPump>>>(hwnd)
|
||||
{
|
||||
if let Some(message_loop) = message_loop.upgrade() {
|
||||
if let Ok(mut message_loop) = message_loop.lock() {
|
||||
if message == MSG_HAVE_WORK {
|
||||
let delay = lparam as i64;
|
||||
message_loop.on_schedule_work(delay);
|
||||
} else {
|
||||
message_loop.on_timer_timeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WM_DESTROY => {
|
||||
let _ = set_user_data::<Weak<Mutex<MainMessageLoopExternalPump>>>(hwnd, None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
DefWindowProcW(hwnd, message, wparam, lparam)
|
||||
}
|
||||
|
||||
pub fn on_run(&mut self) -> bool {
|
||||
let mut msg = Default::default();
|
||||
unsafe {
|
||||
while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn on_quit(&mut self) {
|
||||
unsafe {
|
||||
PostMessageW(ptr::null_mut(), WM_QUIT, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_schedule_message_pump_work(&mut self, delay: i64) {
|
||||
// This method may be called on any thread.
|
||||
unsafe {
|
||||
if let Some(main_thread_target) = self.main_thread_target {
|
||||
PostMessageW(
|
||||
main_thread_target as HWND,
|
||||
MSG_HAVE_WORK,
|
||||
0,
|
||||
delay as LPARAM,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_timer(&mut self, delay: i64) {
|
||||
self.timer_pending = true;
|
||||
if let Some(main_thread_target) = self.main_thread_target {
|
||||
unsafe {
|
||||
SetTimer(main_thread_target as HWND, 1, delay as u32, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill_timer(&mut self) {
|
||||
if self.timer_pending {
|
||||
if let Some(main_thread_target) = self.main_thread_target {
|
||||
unsafe {
|
||||
KillTimer(main_thread_target as HWND, 1);
|
||||
}
|
||||
}
|
||||
self.timer_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_timer_pending(&self) -> bool {
|
||||
self.timer_pending
|
||||
}
|
||||
}
|
||||
46
examples/tests_shared/src/browser/main_message_loop_std.rs
Normal file
46
examples/tests_shared/src/browser/main_message_loop_std.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use super::main_message_loop::*;
|
||||
use cef::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::Foundation::HWND;
|
||||
|
||||
pub struct MainMessageLoopStd;
|
||||
|
||||
impl MainMessageLoopStd {
|
||||
pub fn new() -> Arc<Mutex<Option<Box<dyn MainMessageLoop>>>> {
|
||||
set_main_message_loop(Some(Box::new(MainMessageLoopStd)));
|
||||
get_main_message_loop()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MainMessageLoopStd {
|
||||
fn drop(&mut self) {
|
||||
set_main_message_loop(None);
|
||||
}
|
||||
}
|
||||
|
||||
impl MainMessageLoop for MainMessageLoopStd {
|
||||
fn run(&mut self) -> i32 {
|
||||
run_message_loop();
|
||||
0
|
||||
}
|
||||
|
||||
fn quit(&mut self) {
|
||||
quit_message_loop();
|
||||
}
|
||||
|
||||
fn post_task(&mut self, task: Option<&mut Task>) {
|
||||
post_task(ThreadId::UI, task);
|
||||
}
|
||||
|
||||
fn run_tasks_on_current_thread(&self) -> bool {
|
||||
currently_on(ThreadId::UI) != 0
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn set_current_modeless_dialog(&mut self, _hwnd: HWND) {
|
||||
// Nothing to do here. The Chromium message loop implementation will internally route
|
||||
// dialog messages.
|
||||
}
|
||||
}
|
||||
10
examples/tests_shared/src/browser/mod.rs
Normal file
10
examples/tests_shared/src/browser/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
pub mod client_app_browser;
|
||||
pub mod file_util;
|
||||
pub mod geometry_util;
|
||||
pub mod main_message_loop;
|
||||
pub mod main_message_loop_external_pump;
|
||||
pub mod main_message_loop_std;
|
||||
pub mod resource_util;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod util_win;
|
||||
9
examples/tests_shared/src/browser/resource_util/mod.rs
Normal file
9
examples/tests_shared/src/browser/resource_util/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use win::*;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub mod posix;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub use posix::*;
|
||||
42
examples/tests_shared/src/browser/resource_util/posix.rs
Normal file
42
examples/tests_shared/src/browser/resource_util/posix.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use cef::*;
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
|
||||
pub fn get_resource_directory() -> Option<PathBuf> {
|
||||
let mut path = std::env::current_exe().ok()?;
|
||||
|
||||
// Pop the executable file name.
|
||||
path.pop();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// Pop the MacOS directory.
|
||||
path.pop();
|
||||
path.push("Resources");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
path.push("files");
|
||||
}
|
||||
|
||||
Some(path)
|
||||
}
|
||||
|
||||
pub fn load_binary_resource(resource_name: &str) -> Option<Vec<u8>> {
|
||||
let path = get_resource_directory()?.join(resource_name);
|
||||
let mut file = File::open(path).ok()?;
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data).ok()?;
|
||||
Some(data)
|
||||
}
|
||||
|
||||
pub fn get_binary_resource_reader(resource_name: &str) -> Option<StreamReader> {
|
||||
let path = get_resource_directory()?.join(resource_name);
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = path.to_str()?;
|
||||
let path = CefString::from(path);
|
||||
stream_reader_create_for_file(Some(&CefString::from(path)))
|
||||
}
|
||||
124
examples/tests_shared/src/browser/resource_util/win.rs
Normal file
124
examples/tests_shared/src/browser/resource_util/win.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use crate::browser::util_win::*;
|
||||
use cef::{
|
||||
wrapper::{byte_read_handler::*, resource_manager::*, stream_resource_handler::*},
|
||||
*,
|
||||
};
|
||||
use std::{
|
||||
mem,
|
||||
sync::{Arc, Mutex, OnceLock},
|
||||
};
|
||||
use windows_sys::Win32::System::LibraryLoader::{
|
||||
FindResourceW, LoadResource, LockResource, SizeofResource,
|
||||
};
|
||||
|
||||
pub type GetResourceId = Box<dyn Send + Sync + Fn(&str) -> u16>;
|
||||
|
||||
static INSTANCE: OnceLock<Arc<Mutex<Option<GetResourceId>>>> = OnceLock::new();
|
||||
|
||||
pub fn get_fn_get_resource_id() -> Arc<Mutex<Option<GetResourceId>>> {
|
||||
INSTANCE.get_or_init(|| Arc::new(Mutex::new(None))).clone()
|
||||
}
|
||||
|
||||
pub fn set_fn_get_resource_id(mut get_resource_id: Option<GetResourceId>) -> Option<GetResourceId> {
|
||||
let instance = get_fn_get_resource_id();
|
||||
let Ok(mut instance) = instance.lock() else {
|
||||
return get_resource_id;
|
||||
};
|
||||
mem::swap(&mut *instance, &mut get_resource_id);
|
||||
get_resource_id
|
||||
}
|
||||
|
||||
pub fn load_binary_resource(resource_name: &str) -> Option<Vec<u8>> {
|
||||
let get_resource_id = get_fn_get_resource_id();
|
||||
let get_resource_id = get_resource_id.lock().ok()?;
|
||||
let get_resource_id = get_resource_id.as_ref()?;
|
||||
|
||||
let resource_id = (*get_resource_id)(resource_name);
|
||||
let instance = get_code_module_handle();
|
||||
|
||||
unsafe {
|
||||
// Defined in https://github.com/chromiumembedded/cef/blob/master/tests/cefclient/browser/resource.h
|
||||
const RT_BINARY: u16 = 256;
|
||||
|
||||
let resource = FindResourceW(
|
||||
instance,
|
||||
resource_id as usize as *const _,
|
||||
RT_BINARY as usize as *const _,
|
||||
);
|
||||
if resource.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let data = LoadResource(instance, resource);
|
||||
if data.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size = SizeofResource(instance, resource);
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ptr = LockResource(data);
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(std::slice::from_raw_parts(ptr as *const u8, size as usize).to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_binary_resource_reader(resource_name: &str) -> Option<StreamReader> {
|
||||
let data = load_binary_resource(resource_name)?;
|
||||
let stream = ByteStream::new(data);
|
||||
let mut handler = ByteReadHandler::new(Arc::new(Mutex::new(stream)));
|
||||
stream_reader_create_for_handler(Some(&mut handler))
|
||||
}
|
||||
|
||||
struct BinaryResourceProvider {
|
||||
url_path: String,
|
||||
resource_path_prefix: String,
|
||||
}
|
||||
|
||||
impl BinaryResourceProvider {
|
||||
fn new(url_path: &str, resource_path_prefix: &str) -> Self {
|
||||
Self {
|
||||
url_path: normalize_url_path(url_path),
|
||||
resource_path_prefix: normalize_url_path(resource_path_prefix),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceManagerProvider for BinaryResourceProvider {
|
||||
fn on_request(&self, request: Arc<Mutex<ResourceManagerRequest>>) -> bool {
|
||||
assert_ne!(
|
||||
currently_on(ThreadId::IO),
|
||||
0,
|
||||
"on_request must be called on the IO thread"
|
||||
);
|
||||
|
||||
let Ok(mut request) = request.lock() else {
|
||||
return false;
|
||||
};
|
||||
let url = request.url();
|
||||
let Some(relative_path) = url.strip_prefix(self.url_path.as_str()) else {
|
||||
// Not handled by this provider.
|
||||
return false;
|
||||
};
|
||||
|
||||
let mime_type = request.mime_type_resolver()(url);
|
||||
let relative_path = format!("{}/{relative_path}", self.resource_path_prefix);
|
||||
let handler = get_binary_resource_reader(&relative_path)
|
||||
.map(|stream| StreamResourceHandler::new_with_stream(mime_type, stream));
|
||||
|
||||
request.continue_request(handler);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_binary_resource_provider(
|
||||
url_path: &str,
|
||||
resource_path_prefix: &str,
|
||||
) -> Box<dyn ResourceManagerProvider> {
|
||||
Box::new(BinaryResourceProvider::new(url_path, resource_path_prefix))
|
||||
}
|
||||
256
examples/tests_shared/src/browser/util_win.rs
Normal file
256
examples/tests_shared/src/browser/util_win.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
use std::{ffi::c_void, mem, ptr, sync::OnceLock};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::*,
|
||||
Graphics::Gdi::*,
|
||||
System::{
|
||||
LibraryLoader::{
|
||||
GetModuleHandleExW, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
},
|
||||
Performance::*,
|
||||
SystemServices::*,
|
||||
},
|
||||
UI::{Input::KeyboardAndMouse::*, WindowsAndMessaging::*},
|
||||
};
|
||||
|
||||
pub fn get_time_now() -> u64 {
|
||||
static FREQUENCY: OnceLock<f64> = OnceLock::new();
|
||||
|
||||
let frequency = FREQUENCY.get_or_init(|| {
|
||||
let mut frequency = 0;
|
||||
unsafe { QueryPerformanceFrequency(&mut frequency) };
|
||||
frequency.max(1) as f64 / 1000000.0
|
||||
});
|
||||
|
||||
let mut current_time = 0;
|
||||
unsafe { QueryPerformanceCounter(&mut current_time) };
|
||||
((current_time as f64 / *frequency) as i64).max(0) as u64
|
||||
}
|
||||
|
||||
pub fn set_user_data_ptr(hwnd: HWND, data: *mut c_void) -> *mut c_void {
|
||||
unsafe {
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
let result = SetWindowLongPtrW(hwnd, GWLP_USERDATA, data as isize);
|
||||
assert!(result != 0 || GetLastError() == ERROR_SUCCESS);
|
||||
result as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_user_data<T>(hwnd: HWND, data: Option<T>) -> Option<Box<T>> {
|
||||
let ptr: *mut T = set_user_data_ptr(
|
||||
hwnd,
|
||||
data.map(|data| Box::into_raw(Box::new(data)).cast())
|
||||
.unwrap_or(ptr::null_mut()),
|
||||
)
|
||||
.cast();
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
unsafe { Some(Box::from_raw(ptr)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_user_data<'a, T>(hwnd: HWND) -> Option<&'a mut T> {
|
||||
unsafe {
|
||||
let ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut T;
|
||||
ptr.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_window_proc_ptr(hwnd: HWND, proc: WNDPROC) -> WNDPROC {
|
||||
Some(unsafe {
|
||||
let old = GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
|
||||
assert_ne!(old, 0);
|
||||
if let Some(proc) = proc {
|
||||
let result = SetWindowLongPtrW(hwnd, GWLP_WNDPROC, mem::transmute(proc));
|
||||
assert!(result != 0 || GetLastError() == ERROR_SUCCESS);
|
||||
}
|
||||
mem::transmute(old)
|
||||
})
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Default, Debug)]
|
||||
pub enum EventFlag {
|
||||
#[default]
|
||||
None = 0,
|
||||
CapsLockOn = 1 << 0,
|
||||
ShiftDown = 1 << 1,
|
||||
ControlDown = 1 << 2,
|
||||
AltDown = 1 << 3,
|
||||
LeftMouseButton = 1 << 4,
|
||||
MiddleMouseButton = 1 << 5,
|
||||
RightMouseButton = 1 << 6,
|
||||
CommandDown = 1 << 7,
|
||||
NumLockOn = 1 << 8,
|
||||
IsKeyPad = 1 << 9,
|
||||
IsLeft = 1 << 10,
|
||||
IsRight = 1 << 11,
|
||||
AltGrDown = 1 << 12,
|
||||
IsRepeat = 1 << 13,
|
||||
PrecisionScrollingDelta = 1 << 14,
|
||||
ScrollByPage = 1 << 15,
|
||||
}
|
||||
|
||||
pub fn get_resource_string(id: u32) -> String {
|
||||
let mut buffer = [0u16; 100];
|
||||
String::from_utf16_lossy(unsafe {
|
||||
let len = LoadStringW(
|
||||
get_code_module_handle(),
|
||||
id,
|
||||
buffer.as_mut_ptr(),
|
||||
buffer.len() as i32,
|
||||
) as usize;
|
||||
&buffer[..len]
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_cef_mouse_modifiers(wparam: WPARAM) -> i32 {
|
||||
let mut modifiers = 0;
|
||||
|
||||
if wparam & MK_CONTROL as usize != 0 {
|
||||
modifiers |= EventFlag::ControlDown as i32;
|
||||
}
|
||||
if wparam & MK_SHIFT as usize != 0 {
|
||||
modifiers |= EventFlag::ShiftDown as i32;
|
||||
}
|
||||
if is_key_down(VK_MENU as _) {
|
||||
modifiers |= EventFlag::AltDown as i32;
|
||||
}
|
||||
if wparam & MK_LBUTTON as usize != 0 {
|
||||
modifiers |= EventFlag::LeftMouseButton as i32;
|
||||
}
|
||||
if wparam & MK_MBUTTON as usize != 0 {
|
||||
modifiers |= EventFlag::MiddleMouseButton as i32;
|
||||
}
|
||||
if wparam & MK_RBUTTON as usize != 0 {
|
||||
modifiers |= EventFlag::RightMouseButton as i32;
|
||||
}
|
||||
|
||||
// Low bit set from GetKeyState indicates "toggled".
|
||||
unsafe {
|
||||
if GetKeyState(VK_NUMLOCK as i32) & 1 != 0 {
|
||||
modifiers |= EventFlag::NumLockOn as i32;
|
||||
}
|
||||
if GetKeyState(VK_CAPITAL as i32) & 1 != 0 {
|
||||
modifiers |= EventFlag::CapsLockOn as i32;
|
||||
}
|
||||
}
|
||||
|
||||
modifiers
|
||||
}
|
||||
|
||||
pub fn get_cef_keyboard_modifiers(wparam: WPARAM, lparam: LPARAM) -> i32 {
|
||||
let mut modifiers = 0;
|
||||
|
||||
if is_key_down(VK_SHIFT as _) {
|
||||
modifiers |= EventFlag::ShiftDown as i32;
|
||||
}
|
||||
if is_key_down(VK_CONTROL as _) {
|
||||
modifiers |= EventFlag::ControlDown as i32;
|
||||
}
|
||||
if is_key_down(VK_MENU as _) {
|
||||
modifiers |= EventFlag::AltDown as i32;
|
||||
}
|
||||
|
||||
// Low bit set from GetKeyState indicates "toggled".
|
||||
unsafe {
|
||||
if GetKeyState(VK_NUMLOCK as i32) & 1 != 0 {
|
||||
modifiers |= EventFlag::NumLockOn as i32;
|
||||
}
|
||||
if GetKeyState(VK_CAPITAL as i32) & 1 != 0 {
|
||||
modifiers |= EventFlag::CapsLockOn as i32;
|
||||
}
|
||||
}
|
||||
|
||||
match wparam as VIRTUAL_KEY {
|
||||
VK_RETURN => {
|
||||
if (lparam >> 16) & KF_EXTENDED as isize != 0 {
|
||||
modifiers |= EventFlag::IsKeyPad as i32;
|
||||
}
|
||||
}
|
||||
VK_INSERT | VK_DELETE | VK_HOME | VK_END | VK_PRIOR | VK_NEXT | VK_UP | VK_DOWN
|
||||
| VK_LEFT | VK_RIGHT => {
|
||||
if (lparam >> 16) & KF_EXTENDED as isize == 0 {
|
||||
modifiers |= EventFlag::IsKeyPad as i32;
|
||||
}
|
||||
}
|
||||
VK_NUMLOCK | VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4
|
||||
| VK_NUMPAD5 | VK_NUMPAD6 | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DIVIDE
|
||||
| VK_MULTIPLY | VK_SUBTRACT | VK_ADD | VK_DECIMAL | VK_CLEAR => {
|
||||
modifiers |= EventFlag::IsKeyPad as i32;
|
||||
}
|
||||
VK_SHIFT => {
|
||||
if is_key_down(VK_LSHIFT as _) {
|
||||
modifiers |= EventFlag::IsLeft as i32;
|
||||
} else if is_key_down(VK_RSHIFT as _) {
|
||||
modifiers |= EventFlag::IsRight as i32;
|
||||
}
|
||||
}
|
||||
VK_CONTROL => {
|
||||
if is_key_down(VK_LCONTROL as _) {
|
||||
modifiers |= EventFlag::IsLeft as i32;
|
||||
} else if is_key_down(VK_RCONTROL as _) {
|
||||
modifiers |= EventFlag::IsRight as i32;
|
||||
}
|
||||
}
|
||||
VK_MENU => {
|
||||
if is_key_down(VK_LMENU as _) {
|
||||
modifiers |= EventFlag::IsLeft as i32;
|
||||
} else if is_key_down(VK_RMENU as _) {
|
||||
modifiers |= EventFlag::IsRight as i32;
|
||||
}
|
||||
}
|
||||
VK_LWIN => {
|
||||
modifiers |= EventFlag::IsLeft as i32;
|
||||
}
|
||||
VK_RWIN => {
|
||||
modifiers |= EventFlag::IsRight as i32;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
modifiers
|
||||
}
|
||||
|
||||
pub fn is_key_down(wparam: WPARAM) -> bool {
|
||||
unsafe {
|
||||
let key_state = GetKeyState(wparam as i32) as u16;
|
||||
key_state & 0x8000 != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_device_scale_factor() -> f32 {
|
||||
static SCALE_FACTOR: OnceLock<f32> = OnceLock::new();
|
||||
|
||||
*SCALE_FACTOR.get_or_init(|| unsafe {
|
||||
let screen_dc = GetDC(ptr::null_mut());
|
||||
let dpi_x = GetDeviceCaps(screen_dc, LOGPIXELSX as i32);
|
||||
ReleaseDC(ptr::null_mut(), screen_dc);
|
||||
dpi_x as f32 / 96.0
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code_module_handle() -> HINSTANCE {
|
||||
let mut module: HMODULE = ptr::null_mut();
|
||||
unsafe {
|
||||
let result = GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
get_code_module_handle as *const _,
|
||||
&mut module,
|
||||
);
|
||||
assert_ne!(result, 0);
|
||||
}
|
||||
module
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_time_now() {
|
||||
let time = get_time_now();
|
||||
assert!(time > 0);
|
||||
}
|
||||
}
|
||||
147
examples/tests_shared/src/common/binary_value_utils.rs
Normal file
147
examples/tests_shared/src/common/binary_value_utils.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use cef::*;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::OnceLock,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub const TEST_SEND_PROCESS_MESSAGE: &[u8] = b"testSendProcessMessage";
|
||||
pub const TEST_SEND_SMR_PROCESS_MESSAGE: &[u8] = b"testSendSMRProcessMessage";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MessageId {
|
||||
pub id: u32,
|
||||
}
|
||||
|
||||
impl From<u32> for MessageId {
|
||||
fn from(id: u32) -> Self {
|
||||
Self { id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for MessageId {
|
||||
fn from(bytes: &[u8]) -> Self {
|
||||
assert_eq!(bytes.len(), 4);
|
||||
let mut id = [0; 4];
|
||||
id.copy_from_slice(bytes);
|
||||
Self::from(u32::from_ne_bytes(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MessageId> for [u8; 4] {
|
||||
fn from(id: &MessageId) -> Self {
|
||||
id.id.to_ne_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElapsedMicros {
|
||||
duration: u128,
|
||||
}
|
||||
|
||||
impl ElapsedMicros {
|
||||
pub fn now() -> Self {
|
||||
static START_TIME: OnceLock<Instant> = OnceLock::new();
|
||||
let start_time = START_TIME.get_or_init(Instant::now);
|
||||
|
||||
Self {
|
||||
duration: start_time.elapsed().as_micros(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for ElapsedMicros {
|
||||
fn from(bytes: &[u8]) -> Self {
|
||||
assert_eq!(bytes.len(), 16);
|
||||
let mut duration = [0; 16];
|
||||
duration.copy_from_slice(bytes);
|
||||
Self {
|
||||
duration: u128::from_ne_bytes(duration),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ElapsedMicros> for [u8; 16] {
|
||||
fn from(elapsed: &ElapsedMicros) -> Self {
|
||||
elapsed.duration.to_ne_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ElapsedMicros {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let duration = u64::try_from(self.duration).unwrap_or(u64::MAX);
|
||||
let duration = Duration::from_micros(duration);
|
||||
write!(f, "{duration:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BrowserMessage {
|
||||
pub test_id: MessageId,
|
||||
pub start_time: ElapsedMicros,
|
||||
}
|
||||
|
||||
impl From<&BinaryValue> for BrowserMessage {
|
||||
fn from(message: &BinaryValue) -> Self {
|
||||
assert_eq!(message.size(), 20);
|
||||
|
||||
let mut data = vec![0; message.size()];
|
||||
message.data(Some(&mut data), 0);
|
||||
|
||||
Self {
|
||||
test_id: MessageId::from(&data[0..4]),
|
||||
start_time: ElapsedMicros::from(&data[4..20]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BrowserMessage> for Option<BinaryValue> {
|
||||
fn from(message: &BrowserMessage) -> Self {
|
||||
let mut data = vec![0; 20];
|
||||
|
||||
let test_id: [u8; 4] = (&message.test_id).into();
|
||||
let start_time: [u8; 16] = (&message.start_time).into();
|
||||
|
||||
data[0..4].copy_from_slice(&test_id);
|
||||
data[4..20].copy_from_slice(&start_time);
|
||||
|
||||
binary_value_create(Some(&data))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RendererMessage {
|
||||
pub test_id: MessageId,
|
||||
pub duration: ElapsedMicros,
|
||||
pub start_time: ElapsedMicros,
|
||||
}
|
||||
|
||||
impl From<&BinaryValue> for RendererMessage {
|
||||
fn from(message: &BinaryValue) -> Self {
|
||||
assert_eq!(message.size(), 36);
|
||||
|
||||
let mut data = vec![0; message.size()];
|
||||
message.data(Some(&mut data), 0);
|
||||
|
||||
Self {
|
||||
test_id: MessageId::from(&data[0..4]),
|
||||
duration: ElapsedMicros::from(&data[4..20]),
|
||||
start_time: ElapsedMicros::from(&data[20..36]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RendererMessage> for Option<BinaryValue> {
|
||||
fn from(message: &RendererMessage) -> Self {
|
||||
let mut data = vec![0; 36];
|
||||
|
||||
let test_id: [u8; 4] = (&message.test_id).into();
|
||||
let duration: [u8; 16] = (&message.duration).into();
|
||||
let start_time: [u8; 16] = (&message.start_time).into();
|
||||
|
||||
data[0..4].copy_from_slice(&test_id);
|
||||
data[4..20].copy_from_slice(&duration);
|
||||
data[20..36].copy_from_slice(&start_time);
|
||||
|
||||
binary_value_create(Some(&data))
|
||||
}
|
||||
}
|
||||
66
examples/tests_shared/src/common/client_app.rs
Normal file
66
examples/tests_shared/src/common/client_app.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use cef::*;
|
||||
|
||||
pub const PROCESS_TYPE: &str = "type";
|
||||
pub const RENDERER_PROCESS: &str = "renderer";
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const ZYGOTE_PROCESS: &str = "zygote";
|
||||
|
||||
pub enum ProcessType {
|
||||
Browser,
|
||||
Renderer,
|
||||
#[cfg(target_os = "linux")]
|
||||
Zygote,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<&CommandLine> for ProcessType {
|
||||
fn from(value: &CommandLine) -> Self {
|
||||
let process_type = CefString::from(PROCESS_TYPE);
|
||||
if value.has_switch(Some(&process_type)) == 0 {
|
||||
return Self::Browser;
|
||||
}
|
||||
|
||||
let value = CefString::from(&value.switch_value(Some(&process_type))).to_string();
|
||||
match value.as_str() {
|
||||
RENDERER_PROCESS => Self::Renderer,
|
||||
#[cfg(target_os = "linux")]
|
||||
ZYGOTE_PROCESS => Self::Zygote,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientAppCustomScheme {
|
||||
name: String,
|
||||
options: i32,
|
||||
}
|
||||
|
||||
impl ClientAppCustomScheme {
|
||||
pub fn new(name: &str, options: &[SchemeOptions]) -> Self {
|
||||
let options = options.iter().fold(0, |acc, opt| acc | opt.get_raw()) as i32;
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_app! {
|
||||
pub struct ClientApp {
|
||||
custom_schemes: Vec<ClientAppCustomScheme>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
let Some(registrar) = registrar else {
|
||||
return;
|
||||
};
|
||||
|
||||
for scheme in &self.custom_schemes {
|
||||
let name = CefString::from(scheme.name.as_str());
|
||||
registrar.add_custom_scheme(Some(&name), scheme.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
examples/tests_shared/src/common/client_app_other.rs
Normal file
13
examples/tests_shared/src/common/client_app_other.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use cef::*;
|
||||
|
||||
wrap_app! {
|
||||
pub struct ClientAppOther {
|
||||
base: App,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
self.base.on_register_custom_schemes(registrar);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
examples/tests_shared/src/common/client_switches.rs
Normal file
52
examples/tests_shared/src/common/client_switches.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! CEF and Chromium support a wide range of command-line switches. This file
|
||||
//! only contains command-line switches specific to the cefclient application.
|
||||
//! View CEF/Chromium documentation or search for *_switches.cc files in the
|
||||
//! Chromium source code to identify other existing command-line switches.
|
||||
|
||||
pub const MULTI_THREADED_MESSAGE_LOOP: &str = "multi-threaded-message-loop";
|
||||
pub const EXTERNAL_MESSAGE_PUMP: &str = "external-message-pump";
|
||||
pub const CACHE_PATH: &str = "cache-path";
|
||||
pub const URL: &str = "url";
|
||||
pub const OFF_SCREEN_RENDERING_ENABLED: &str = "off-screen-rendering-enabled";
|
||||
pub const OFF_SCREEN_FRAME_RATE: &str = "off-screen-frame-rate";
|
||||
pub const TRANSPARENT_PAINTING_ENABLED: &str = "transparent-painting-enabled";
|
||||
pub const SHOW_UPDATE_RECT: &str = "show-update-rect";
|
||||
pub const FAKE_SCREEN_BOUNDS: &str = "fake-screen-bounds";
|
||||
pub const SHARED_TEXTURE_ENABLED: &str = "shared-texture-enabled";
|
||||
pub const EXTERNAL_BEGIN_FRAME_ENABLED: &str = "external-begin-frame-enabled";
|
||||
pub const MOUSE_CURSOR_CHANGE_DISABLED: &str = "mouse-cursor-change-disabled";
|
||||
pub const OFFLINE: &str = "offline";
|
||||
pub const FILTER_CHROME_COMMANDS: &str = "filter-chrome-commands";
|
||||
pub const REQUEST_CONTEXT_PER_BROWSER: &str = "request-context-per-browser";
|
||||
pub const REQUEST_CONTEXT_SHARED_CACHE: &str = "request-context-shared-cache";
|
||||
pub const BACKGROUND_COLOR: &str = "background-color";
|
||||
pub const ENABLE_GPU: &str = "enable-gpu";
|
||||
pub const FILTER_URL: &str = "filter-url";
|
||||
pub const USE_VIEWS: &str = "use-views";
|
||||
pub const USE_NATIVE: &str = "use-native";
|
||||
pub const HIDE_FRAME: &str = "hide-frame";
|
||||
pub const HIDE_CONTROLS: &str = "hide-controls";
|
||||
pub const HIDE_OVERLAYS: &str = "hide-overlays";
|
||||
pub const ALWAYS_ON_TOP: &str = "always-on-top";
|
||||
pub const HIDE_TOP_MENU: &str = "hide-top-menu";
|
||||
pub const SSL_CLIENT_CERTIFICATE: &str = "ssl-client-certificate";
|
||||
pub const CRL_SETS_PATH: &str = "crl-sets-path";
|
||||
pub const NO_ACTIVATE: &str = "no-activate";
|
||||
pub const SHOW_CHROME_TOOLBAR: &str = "show-chrome-toolbar";
|
||||
pub const INITIAL_SHOW_STATE: &str = "initial-show-state";
|
||||
pub const USE_DEFAULT_POPUP: &str = "use-default-popup";
|
||||
pub const USE_CLIENT_DIALOGS: &str = "use-client-dialogs";
|
||||
pub const USE_TEST_HTTP_SERVER: &str = "use-test-http-server";
|
||||
pub const SHOW_WINDOW_BUTTONS: &str = "show-window-buttons";
|
||||
pub const USE_WINDOW_MODAL_DIALOG: &str = "use-window-modal-dialog";
|
||||
pub const USE_BOTTOM_CONTROLS: &str = "use-bottom-controls";
|
||||
pub const HIDE_PIP_FRAME: &str = "hide-pip-frame";
|
||||
pub const MOVE_PIP_ENABLED: &str = "move-pip-enabled";
|
||||
pub const HIDE_CHROME_BUBBLES: &str = "hide-chrome-bubbles";
|
||||
pub const HIDE_WINDOW_ON_CLOSE: &str = "hide-window-on-close";
|
||||
pub const ACCEPTS_FIRST_MOUSE: &str = "accepts-first-mouse";
|
||||
pub const USE_ALLOY_STYLE: &str = "use-alloy-style";
|
||||
pub const USE_CHROME_STYLE_WINDOW: &str = "use-chrome-style-window";
|
||||
pub const SHOW_OVERLAY_BROWSER: &str = "show-overlay-browser";
|
||||
pub const USE_ANGLE: &str = "use-angle";
|
||||
pub const OZONE_PLATFORM: &str = "ozone-platform";
|
||||
4
examples/tests_shared/src/common/mod.rs
Normal file
4
examples/tests_shared/src/common/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod binary_value_utils;
|
||||
pub mod client_app;
|
||||
pub mod client_app_other;
|
||||
pub mod client_switches;
|
||||
6
examples/tests_shared/src/lib.rs
Normal file
6
examples/tests_shared/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod browser;
|
||||
pub mod common;
|
||||
pub mod renderer;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod process_helper_mac;
|
||||
44
examples/tests_shared/src/process_helper_mac.rs
Normal file
44
examples/tests_shared/src/process_helper_mac.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use crate::{
|
||||
common::{client_app::*, client_app_other::*},
|
||||
renderer::client_app_renderer::*,
|
||||
};
|
||||
use cef::{args::Args, *};
|
||||
|
||||
pub fn run_main(
|
||||
args: Args,
|
||||
custom_schemes: Vec<ClientAppCustomScheme>,
|
||||
app_renderer_delegates: Vec<Box<dyn Delegate>>,
|
||||
) -> Result<(), i32> {
|
||||
#[cfg(feature = "sandbox")]
|
||||
let _sandbox = {
|
||||
let mut sandbox = cef::sandbox::Sandbox::new();
|
||||
sandbox.initialize(args.as_main_args());
|
||||
sandbox
|
||||
};
|
||||
|
||||
let _loader = {
|
||||
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true);
|
||||
assert!(loader.load());
|
||||
loader
|
||||
};
|
||||
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
|
||||
let app = ClientApp::new(custom_schemes);
|
||||
let mut app = match args.as_cmd_line().map(|cmd| ProcessType::from(&cmd)) {
|
||||
Some(ProcessType::Renderer) => {
|
||||
let app_renderer = ClientAppRenderer::new(app_renderer_delegates);
|
||||
ClientAppRendererApp::new(app, app_renderer)
|
||||
}
|
||||
_ => ClientAppOther::new(app),
|
||||
};
|
||||
|
||||
match execute_process(
|
||||
Some(args.as_main_args()),
|
||||
Some(&mut app),
|
||||
std::ptr::null_mut(),
|
||||
) {
|
||||
0 => Ok(()),
|
||||
err => Err(err),
|
||||
}
|
||||
}
|
||||
255
examples/tests_shared/src/renderer/client_app_renderer.rs
Normal file
255
examples/tests_shared/src/renderer/client_app_renderer.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
use cef::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait Delegate: Send {
|
||||
fn on_web_kit_initialized(&self, _app: &Arc<ClientAppRenderer>) {}
|
||||
|
||||
fn on_browser_created(
|
||||
&self,
|
||||
_app: &Arc<ClientAppRenderer>,
|
||||
_browser: Option<&Browser>,
|
||||
_extra_info: Option<&DictionaryValue>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_browser_destroyed(&self, _app: &Arc<ClientAppRenderer>, _browser: Option<&Browser>) {}
|
||||
|
||||
fn load_handler(&self, _app: &Arc<ClientAppRenderer>) -> Option<LoadHandler> {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_context_created(
|
||||
&self,
|
||||
_app: &Arc<ClientAppRenderer>,
|
||||
_browser: Option<&Browser>,
|
||||
_frame: Option<&Frame>,
|
||||
_context: Option<&V8Context>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_context_released(
|
||||
&self,
|
||||
_app: &Arc<ClientAppRenderer>,
|
||||
_browser: Option<&Browser>,
|
||||
_frame: Option<&Frame>,
|
||||
_context: Option<&V8Context>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_uncaught_exception(
|
||||
&self,
|
||||
_app: &Arc<ClientAppRenderer>,
|
||||
_browser: Option<&Browser>,
|
||||
_frame: Option<&Frame>,
|
||||
_context: Option<&V8Context>,
|
||||
_exception: Option<&V8Exception>,
|
||||
_stack_trace: Option<&V8StackTrace>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_focused_node_changed(
|
||||
&self,
|
||||
_app: &Arc<ClientAppRenderer>,
|
||||
_browser: Option<&Browser>,
|
||||
_frame: Option<&Frame>,
|
||||
_node: Option<&Domnode>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_process_message_received(
|
||||
&self,
|
||||
_app: &Arc<ClientAppRenderer>,
|
||||
_browser: Option<&Browser>,
|
||||
_frame: Option<&Frame>,
|
||||
_source_process: ProcessId,
|
||||
_message: Option<&ProcessMessage>,
|
||||
) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientAppRenderer {
|
||||
delegates: Vec<Box<dyn Delegate>>,
|
||||
}
|
||||
|
||||
impl ClientAppRenderer {
|
||||
pub fn new(delegates: Vec<Box<dyn Delegate>>) -> Arc<Self> {
|
||||
Arc::new(Self { delegates })
|
||||
}
|
||||
|
||||
pub fn delegates(&self) -> &[Box<dyn Delegate>] {
|
||||
&self.delegates
|
||||
}
|
||||
}
|
||||
|
||||
wrap_render_process_handler! {
|
||||
struct ClientAppRendererRenderProcessHandler {
|
||||
client_app_renderer: Arc<ClientAppRenderer>,
|
||||
}
|
||||
|
||||
impl RenderProcessHandler {
|
||||
fn on_web_kit_initialized(&self) {
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_web_kit_initialized(&self.client_app_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_browser_created(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
extra_info: Option<&mut DictionaryValue>,
|
||||
) {
|
||||
let browser = browser.cloned();
|
||||
let extra_info = extra_info.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_browser_created(
|
||||
&self.client_app_renderer,
|
||||
browser.as_ref(),
|
||||
extra_info.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_browser_destroyed(&self, browser: Option<&mut Browser>) {
|
||||
let browser = browser.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_browser_destroyed(&self.client_app_renderer, browser.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn load_handler(&self) -> Option<LoadHandler> {
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
if let Some(load_handler) = delegate.load_handler(&self.client_app_renderer) {
|
||||
return Some(load_handler);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn on_context_created(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
context: Option<&mut V8Context>,
|
||||
) {
|
||||
let browser = browser.cloned();
|
||||
let frame = frame.cloned();
|
||||
let context = context.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_context_created(
|
||||
&self.client_app_renderer,
|
||||
browser.as_ref(),
|
||||
frame.as_ref(),
|
||||
context.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_context_released(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
context: Option<&mut V8Context>,
|
||||
) {
|
||||
let browser = browser.cloned();
|
||||
let frame = frame.cloned();
|
||||
let context = context.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_context_released(
|
||||
&self.client_app_renderer,
|
||||
browser.as_ref(),
|
||||
frame.as_ref(),
|
||||
context.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_uncaught_exception(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
context: Option<&mut V8Context>,
|
||||
exception: Option<&mut V8Exception>,
|
||||
stack_trace: Option<&mut V8StackTrace>,
|
||||
) {
|
||||
let browser = browser.cloned();
|
||||
let frame = frame.cloned();
|
||||
let context = context.cloned();
|
||||
let exception = exception.cloned();
|
||||
let stack_trace = stack_trace.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_uncaught_exception(
|
||||
&self.client_app_renderer,
|
||||
browser.as_ref(),
|
||||
frame.as_ref(),
|
||||
context.as_ref(),
|
||||
exception.as_ref(),
|
||||
stack_trace.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_focused_node_changed(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
node: Option<&mut Domnode>,
|
||||
) {
|
||||
let browser = browser.cloned();
|
||||
let frame = frame.cloned();
|
||||
let node = node.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
delegate.on_focused_node_changed(
|
||||
&self.client_app_renderer,
|
||||
browser.as_ref(),
|
||||
frame.as_ref(),
|
||||
node.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_process_message_received(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
source_process: ProcessId,
|
||||
message: Option<&mut ProcessMessage>,
|
||||
) -> i32 {
|
||||
let browser = browser.cloned();
|
||||
let frame = frame.cloned();
|
||||
let message = message.cloned();
|
||||
for delegate in self.client_app_renderer.delegates() {
|
||||
let handled = delegate.on_process_message_received(
|
||||
&self.client_app_renderer,
|
||||
browser.as_ref(),
|
||||
frame.as_ref(),
|
||||
source_process,
|
||||
message.as_ref(),
|
||||
);
|
||||
if handled != 0 {
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_app! {
|
||||
pub struct ClientAppRendererApp {
|
||||
base: App,
|
||||
client_app_renderer: Arc<ClientAppRenderer>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
self.base.on_register_custom_schemes(registrar);
|
||||
}
|
||||
|
||||
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
|
||||
Some(ClientAppRendererRenderProcessHandler::new(
|
||||
self.client_app_renderer.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
1
examples/tests_shared/src/renderer/mod.rs
Normal file
1
examples/tests_shared/src/renderer/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod client_app_renderer;
|
||||
@@ -19,7 +19,7 @@ fn default_version() -> &'static str {
|
||||
fn default_download_url() -> &'static str {
|
||||
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_DOWNLOAD_URL
|
||||
.get_or_init(|| download_cef::default_download_url())
|
||||
.get_or_init(download_cef::default_download_url)
|
||||
.as_str()
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ type Result<T> = std::result::Result<T, Error>;
|
||||
fn default_download_url() -> &'static str {
|
||||
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_DOWNLOAD_URL
|
||||
.get_or_init(|| download_cef::default_download_url())
|
||||
.get_or_init(download_cef::default_download_url)
|
||||
.as_str()
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ fn default_version() -> &'static str {
|
||||
fn default_download_url() -> &'static str {
|
||||
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_DOWNLOAD_URL
|
||||
.get_or_init(|| download_cef::default_download_url())
|
||||
.get_or_init(download_cef::default_download_url)
|
||||
.as_str()
|
||||
}
|
||||
|
||||
|
||||
@@ -2720,6 +2720,7 @@ fn make_my_struct() -> {rust_name} {{
|
||||
$($generic_type: $first_generic_type_bound $(+ $generic_type_bound)*,)+
|
||||
)?
|
||||
{
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new($($field_name: $field_type),*) -> #rust_name {
|
||||
#rust_name::new(
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user