summaryrefslogtreecommitdiffstats
path: root/xpcom/rust
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /xpcom/rust
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/rust')
-rw-r--r--xpcom/rust/gecko_logger/Cargo.toml12
-rw-r--r--xpcom/rust/gecko_logger/src/lib.rs256
-rw-r--r--xpcom/rust/gkrust_utils/Cargo.toml9
-rw-r--r--xpcom/rust/gkrust_utils/cbindgen.toml31
-rw-r--r--xpcom/rust/gkrust_utils/src/lib.rs24
-rw-r--r--xpcom/rust/gtest/bench-collections/Bench.cpp297
-rw-r--r--xpcom/rust/gtest/bench-collections/Cargo.toml12
-rw-r--r--xpcom/rust/gtest/bench-collections/bench.rs101
-rw-r--r--xpcom/rust/gtest/moz.build14
-rw-r--r--xpcom/rust/gtest/moz_task/Cargo.toml13
-rw-r--r--xpcom/rust/gtest/moz_task/TestMozTask.cpp14
-rw-r--r--xpcom/rust/gtest/moz_task/test.rs78
-rw-r--r--xpcom/rust/gtest/nsstring/Cargo.toml12
-rw-r--r--xpcom/rust/gtest/nsstring/TestnsString.cpp177
-rw-r--r--xpcom/rust/gtest/nsstring/test.rs131
-rw-r--r--xpcom/rust/gtest/xpcom/Cargo.toml14
-rw-r--r--xpcom/rust/gtest/xpcom/TestXpcom.cpp66
-rw-r--r--xpcom/rust/gtest/xpcom/test.rs131
-rw-r--r--xpcom/rust/moz_task/Cargo.toml16
-rw-r--r--xpcom/rust/moz_task/src/dispatcher.rs153
-rw-r--r--xpcom/rust/moz_task/src/event_loop.rs66
-rw-r--r--xpcom/rust/moz_task/src/executor.rs291
-rw-r--r--xpcom/rust/moz_task/src/lib.rs378
-rw-r--r--xpcom/rust/nserror/Cargo.toml11
-rw-r--r--xpcom/rust/nserror/src/lib.rs79
-rw-r--r--xpcom/rust/nsstring/Cargo.toml14
-rw-r--r--xpcom/rust/nsstring/src/conversions.rs751
-rw-r--r--xpcom/rust/nsstring/src/lib.rs1543
-rw-r--r--xpcom/rust/xpcom/Cargo.toml20
-rw-r--r--xpcom/rust/xpcom/src/base.rs59
-rw-r--r--xpcom/rust/xpcom/src/components.rs23
-rw-r--r--xpcom/rust/xpcom/src/interfaces/idl.rs12
-rw-r--r--xpcom/rust/xpcom/src/interfaces/mod.rs31
-rw-r--r--xpcom/rust/xpcom/src/interfaces/nonidl.rs180
-rw-r--r--xpcom/rust/xpcom/src/lib.rs43
-rw-r--r--xpcom/rust/xpcom/src/method.rs241
-rw-r--r--xpcom/rust/xpcom/src/promise.rs62
-rw-r--r--xpcom/rust/xpcom/src/reexports.rs52
-rw-r--r--xpcom/rust/xpcom/src/refptr.rs388
-rw-r--r--xpcom/rust/xpcom/src/statics.rs75
-rw-r--r--xpcom/rust/xpcom/xpcom_macros/Cargo.toml16
-rw-r--r--xpcom/rust/xpcom/xpcom_macros/src/lib.rs818
42 files changed, 6714 insertions, 0 deletions
diff --git a/xpcom/rust/gecko_logger/Cargo.toml b/xpcom/rust/gecko_logger/Cargo.toml
new file mode 100644
index 0000000000..2f8ca84a97
--- /dev/null
+++ b/xpcom/rust/gecko_logger/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "gecko_logger"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+lazy_static = "1"
+log = {version = "0.4", features = ["release_max_level_info"]}
+env_logger = {version = "0.10", default-features = false, features = ["color"]} # disable `regex` to reduce code size
+app_services_logger = { path = "../../../services/common/app_services_logger" }
diff --git a/xpcom/rust/gecko_logger/src/lib.rs b/xpcom/rust/gecko_logger/src/lib.rs
new file mode 100644
index 0000000000..5da85b9d89
--- /dev/null
+++ b/xpcom/rust/gecko_logger/src/lib.rs
@@ -0,0 +1,256 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This provides a way to direct rust logging into the gecko logger.
+
+#[macro_use]
+extern crate lazy_static;
+
+use app_services_logger::{AppServicesLogger, LOGGERS_BY_TARGET};
+use log::Log;
+use log::{Level, LevelFilter};
+use std::boxed::Box;
+use std::collections::HashMap;
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use std::os::raw::c_int;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::RwLock;
+use std::{cmp, env};
+
+extern "C" {
+ fn ExternMozLog(tag: *const c_char, prio: c_int, text: *const c_char);
+ fn gfx_critical_note(msg: *const c_char);
+ #[cfg(target_os = "android")]
+ fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
+}
+
+lazy_static! {
+ // This could be a proper static once [1] is fixed or parking_lot's const fn
+ // support is not nightly-only.
+ //
+ // [1]: https://github.com/rust-lang/rust/issues/73714
+ static ref LOG_MODULE_MAP: RwLock<HashMap<String, (LevelFilter, bool)>> = RwLock::new(HashMap::new());
+}
+
+/// This tells us whether LOG_MODULE_MAP is possibly non-empty.
+static LOGGING_ACTIVE: AtomicBool = AtomicBool::new(false);
+
+/// This function searches for the module's name in the hashmap. If that is not
+/// found, it proceeds to search for the parent modules.
+/// It returns a tuple containing the matched string, if the matched module
+/// was a pattern match, and the level we found in the hashmap.
+/// If none is found, it will return the module's name and LevelFilter::Off
+fn get_level_for_module<'a>(
+ map: &HashMap<String, (LevelFilter, bool)>,
+ key: &'a str,
+) -> (&'a str, bool, LevelFilter) {
+ if let Some((level, is_pattern_match)) = map.get(key) {
+ return (key, *is_pattern_match, level.clone());
+ }
+
+ let mut mod_name = &key[..];
+ while let Some(pos) = mod_name.rfind("::") {
+ mod_name = &mod_name[..pos];
+ if let Some((level, is_pattern_match)) = map.get(mod_name) {
+ return (mod_name, *is_pattern_match, level.clone());
+ }
+ }
+
+ return (key, false, LevelFilter::Off);
+}
+
+/// This function takes a record to maybe log to Gecko.
+/// It returns true if the record was handled by Gecko's logging, and false
+/// otherwise.
+pub fn log_to_gecko(record: &log::Record) -> bool {
+ if !LOGGING_ACTIVE.load(Ordering::Relaxed) {
+ return false;
+ }
+
+ let key = match record.module_path() {
+ Some(key) => key,
+ None => return false,
+ };
+
+ let (mod_name, is_pattern_match, level) = {
+ let map = LOG_MODULE_MAP.read().unwrap();
+ get_level_for_module(&map, &key)
+ };
+
+ if level == LevelFilter::Off {
+ return false;
+ }
+
+ if level < record.metadata().level() {
+ return false;
+ }
+
+ // Map the log::Level to mozilla::LogLevel.
+ let moz_log_level = match record.metadata().level() {
+ Level::Error => 1, // Error
+ Level::Warn => 2, // Warning
+ Level::Info => 3, // Info
+ Level::Debug => 4, // Debug
+ Level::Trace => 5, // Verbose
+ };
+
+ // If it was a pattern match, we need to append ::* to the matched string.
+ let (tag, msg) = if is_pattern_match {
+ (
+ CString::new(format!("{}::*", mod_name)).unwrap(),
+ CString::new(format!("[{}] {}", key, record.args())).unwrap(),
+ )
+ } else {
+ (
+ CString::new(key).unwrap(),
+ CString::new(format!("{}", record.args())).unwrap(),
+ )
+ };
+
+ unsafe {
+ ExternMozLog(tag.as_ptr(), moz_log_level, msg.as_ptr());
+ }
+
+ return true;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn set_rust_log_level(module: *const c_char, level: u8) {
+ // Convert the Gecko level to a rust LevelFilter.
+ let rust_level = match level {
+ 1 => LevelFilter::Error,
+ 2 => LevelFilter::Warn,
+ 3 => LevelFilter::Info,
+ 4 => LevelFilter::Debug,
+ 5 => LevelFilter::Trace,
+ _ => LevelFilter::Off,
+ };
+
+ // This is the name of the rust module that we're trying to log in Gecko.
+ let mut mod_name = CStr::from_ptr(module).to_string_lossy().into_owned();
+
+ let is_pattern_match = mod_name.ends_with("::*");
+
+ // If this is a pattern, remove the last "::*" from it so we can search it
+ // in the map.
+ if is_pattern_match {
+ let len = mod_name.len() - 3;
+ mod_name.truncate(len);
+ }
+
+ LOGGING_ACTIVE.store(true, Ordering::Relaxed);
+ let mut map = LOG_MODULE_MAP.write().unwrap();
+ map.insert(mod_name, (rust_level, is_pattern_match));
+
+ // Figure out the max level of all the modules.
+ let max = map
+ .values()
+ .map(|(lvl, _)| lvl)
+ .max()
+ .unwrap_or(&LevelFilter::Off);
+ log::set_max_level(*max);
+}
+
+pub struct GeckoLogger {
+ logger: env_logger::Logger,
+}
+
+impl GeckoLogger {
+ pub fn new() -> GeckoLogger {
+ let mut builder = env_logger::Builder::new();
+ let default_level = if cfg!(debug_assertions) {
+ "warn"
+ } else {
+ "error"
+ };
+ let logger = match env::var("RUST_LOG") {
+ Ok(v) => builder.parse_filters(&v).build(),
+ _ => builder.parse_filters(default_level).build(),
+ };
+
+ GeckoLogger { logger }
+ }
+
+ pub fn init() -> Result<(), log::SetLoggerError> {
+ let gecko_logger = Self::new();
+
+ // The max level may have already been set by gecko_logger. Don't
+ // set it to a lower level.
+ let level = cmp::max(log::max_level(), gecko_logger.logger.filter());
+ log::set_max_level(level);
+ log::set_boxed_logger(Box::new(gecko_logger))
+ }
+
+ fn should_log_to_app_services(target: &str) -> bool {
+ return AppServicesLogger::is_app_services_logger_registered(target.into());
+ }
+
+ fn maybe_log_to_app_services(&self, record: &log::Record) {
+ if Self::should_log_to_app_services(record.target()) {
+ if let Some(l) = LOGGERS_BY_TARGET.read().unwrap().get(record.target()) {
+ l.log(record);
+ }
+ }
+ }
+
+ fn should_log_to_gfx_critical_note(record: &log::Record) -> bool {
+ record.level() == log::Level::Error && record.target().contains("webrender")
+ }
+
+ fn maybe_log_to_gfx_critical_note(&self, record: &log::Record) {
+ if Self::should_log_to_gfx_critical_note(record) {
+ let msg = CString::new(format!("{}", record.args())).unwrap();
+ unsafe {
+ gfx_critical_note(msg.as_ptr());
+ }
+ }
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn log_out(&self, record: &log::Record) {
+ // If the log wasn't handled by the gecko platform logger, just pass it
+ // to the env_logger.
+ if !log_to_gecko(record) {
+ self.logger.log(record);
+ }
+ }
+
+ #[cfg(target_os = "android")]
+ fn log_out(&self, record: &log::Record) {
+ if !self.logger.matches(record) {
+ return;
+ }
+
+ let msg = CString::new(format!("{}", record.args())).unwrap();
+ let tag = CString::new(record.module_path().unwrap()).unwrap();
+ let prio = match record.metadata().level() {
+ Level::Error => 6, /* ERROR */
+ Level::Warn => 5, /* WARN */
+ Level::Info => 4, /* INFO */
+ Level::Debug => 3, /* DEBUG */
+ Level::Trace => 2, /* VERBOSE */
+ };
+ // Output log directly to android log, since env_logger can output log
+ // only to stderr or stdout.
+ unsafe {
+ __android_log_write(prio, tag.as_ptr(), msg.as_ptr());
+ }
+ }
+}
+
+impl log::Log for GeckoLogger {
+ fn enabled(&self, metadata: &log::Metadata) -> bool {
+ self.logger.enabled(metadata) || GeckoLogger::should_log_to_app_services(metadata.target())
+ }
+
+ fn log(&self, record: &log::Record) {
+ // Forward log to gfxCriticalNote, if the log should be in gfx crash log.
+ self.maybe_log_to_gfx_critical_note(record);
+ self.maybe_log_to_app_services(record);
+ self.log_out(record);
+ }
+
+ fn flush(&self) {}
+}
diff --git a/xpcom/rust/gkrust_utils/Cargo.toml b/xpcom/rust/gkrust_utils/Cargo.toml
new file mode 100644
index 0000000000..e398725260
--- /dev/null
+++ b/xpcom/rust/gkrust_utils/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "gkrust_utils"
+version = "0.1.0"
+authors = ["Jonathan Kingston <jkt@mozilla.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+semver = "1.0"
+nsstring = { path = "../nsstring" }
diff --git a/xpcom/rust/gkrust_utils/cbindgen.toml b/xpcom/rust/gkrust_utils/cbindgen.toml
new file mode 100644
index 0000000000..967fbefd47
--- /dev/null
+++ b/xpcom/rust/gkrust_utils/cbindgen.toml
@@ -0,0 +1,31 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
+ * To generate this file:
+ * 1. Get the latest cbindgen using `cargo install --force cbindgen`
+ * a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
+ * 2. Run `rustup run nightly cbindgen xpcom/rust/gkrust_utils --lockfile Cargo.lock --crate gkrust_utils -o xpcom/base/gk_rust_utils_ffi_generated.h`
+ */
+#include "nsError.h"
+#include "nsString.h"
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla"]
+
+[export]
+# Skip constants because we don't have any
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions"]
+
+[enum]
+add_sentinel = true
+derive_helper_methods = true
+
+[defines]
+"target_os = windows" = "XP_WIN"
+"target_os = macos" = "XP_MACOSX"
+"target_os = android" = "ANDROID"
diff --git a/xpcom/rust/gkrust_utils/src/lib.rs b/xpcom/rust/gkrust_utils/src/lib.rs
new file mode 100644
index 0000000000..c519d7e002
--- /dev/null
+++ b/xpcom/rust/gkrust_utils/src/lib.rs
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nsstring;
+extern crate semver;
+use nsstring::nsACString;
+
+#[no_mangle]
+pub unsafe extern "C" fn GkRustUtils_ParseSemVer(
+ ver: &nsACString,
+ out_major: *mut u64,
+ out_minor: *mut u64,
+ out_patch: *mut u64,
+) -> bool {
+ let version = match semver::Version::parse(&ver.to_utf8()) {
+ Ok(ver) => ver,
+ Err(_) => return false,
+ };
+ *out_major = version.major;
+ *out_minor = version.minor;
+ *out_patch = version.patch;
+ true
+}
diff --git a/xpcom/rust/gtest/bench-collections/Bench.cpp b/xpcom/rust/gtest/bench-collections/Bench.cpp
new file mode 100644
index 0000000000..41152470c7
--- /dev/null
+++ b/xpcom/rust/gtest/bench-collections/Bench.cpp
@@ -0,0 +1,297 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Overview
+// --------
+// This file measures the speed of various implementations of C++ and Rust
+// collections (hash tables, etc.) used within the codebase. There are a small
+// number of benchmarks for each collection type; each benchmark tests certain
+// operations (insertion, lookup, iteration, etc.) More benchmarks could easily
+// be envisioned, but this small number is enough to characterize the major
+// differences between implementations, while keeping the file size and
+// complexity low.
+//
+// Details
+// -------
+// The file uses `MOZ_GTEST_BENCH_F` so that results are integrated into
+// PerfHerder. It is also designed so that individual test benchmarks can be
+// run under a profiler.
+//
+// The C++ code uses `MOZ_RELEASE_ASSERT` extensively to check values and
+// ensure operations aren't optimized away by the compiler. The Rust code uses
+// `assert!()`. These should be roughly equivalent, but aren't guaranteed to be
+// the same. As a result, the intra-C++ comparisons should be reliable, and the
+// intra-Rust comparisons should be reliable, but the C++ vs. Rust comparisons
+// may be less reliable.
+//
+// Note that the Rust implementations run very slowly without --enable-release.
+//
+// Profiling
+// ---------
+// If you want to measure a particular implementation under a profiler such as
+// Callgrind, do something like this:
+//
+// MOZ_RUN_GTEST=1 GTEST_FILTER='*BenchCollections*$IMPL*'
+// valgrind --tool=callgrind --callgrind-out-file=clgout
+// $OBJDIR/dist/bin/firefox -unittest
+// callgrind_annotate --auto=yes clgout > clgann
+//
+// where $IMPL is part of an implementation name in a test (e.g. "PLDHash",
+// "MozHash") and $OBJDIR is an objdir containing a --enable-release build.
+//
+// Note that multiple processes are spawned, so `clgout` gets overwritten
+// multiple times, but the last process to write its profiling data to file is
+// the one of interest. (Alternatively, use --callgrind-out-file=clgout.%p to
+// get separate output files for each process, with a PID suffix.)
+
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+#include "mozilla/AllocPolicy.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/TimeStamp.h"
+#include "PLDHashTable.h"
+#include <unordered_set>
+
+using namespace mozilla;
+
+// This function gives a pseudo-random sequence with the following properties:
+// - Deterministic and platform-independent.
+// - No duplicates in the first VALS_LEN results, which is useful for ensuring
+// the tables get to a particular size, and also for guaranteeing lookups
+// that fail.
+static uintptr_t MyRand() {
+ static uintptr_t s = 0;
+ s = s * 1103515245 + 12345;
+ return s;
+}
+
+// Keep this in sync with Params in bench.rs.
+struct Params {
+ const char* mConfigName;
+ size_t mNumInserts; // Insert this many unique keys
+ size_t mNumSuccessfulLookups; // Does mNumInserts lookups each time
+ size_t mNumFailingLookups; // Does mNumInserts lookups each time
+ size_t mNumIterations; // Iterates the full table each time
+ bool mRemoveInserts; // Remove all entries at end?
+};
+
+// We don't use std::unordered_{set,map}, but it's an interesting thing to
+// benchmark against.
+//
+// Keep this in sync with all the other Bench_*() functions.
+static void Bench_Cpp_unordered_set(const Params* aParams, void** aVals,
+ size_t aLen) {
+ std::unordered_set<void*> hs;
+
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ hs.insert(aVals[j]);
+ }
+
+ for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) {
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ MOZ_RELEASE_ASSERT(hs.find(aVals[j]) != hs.end());
+ }
+ }
+
+ for (size_t i = 0; i < aParams->mNumFailingLookups; i++) {
+ for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) {
+ MOZ_RELEASE_ASSERT(hs.find(aVals[j]) == hs.end());
+ }
+ }
+
+ for (size_t i = 0; i < aParams->mNumIterations; i++) {
+ size_t n = 0;
+ for (const auto& elem : hs) {
+ (void)elem;
+ n++;
+ }
+ MOZ_RELEASE_ASSERT(aParams->mNumInserts == n);
+ MOZ_RELEASE_ASSERT(hs.size() == n);
+ }
+
+ if (aParams->mRemoveInserts) {
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ MOZ_RELEASE_ASSERT(hs.erase(aVals[j]) == 1);
+ }
+ MOZ_RELEASE_ASSERT(hs.size() == 0);
+ } else {
+ MOZ_RELEASE_ASSERT(hs.size() == aParams->mNumInserts);
+ }
+}
+
+// Keep this in sync with all the other Bench_*() functions.
+static void Bench_Cpp_PLDHashTable(const Params* aParams, void** aVals,
+ size_t aLen) {
+ PLDHashTable hs(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub));
+
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ auto entry = static_cast<PLDHashEntryStub*>(hs.Add(aVals[j]));
+ MOZ_RELEASE_ASSERT(!entry->key);
+ entry->key = aVals[j];
+ }
+
+ for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) {
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ MOZ_RELEASE_ASSERT(hs.Search(aVals[j]));
+ }
+ }
+
+ for (size_t i = 0; i < aParams->mNumFailingLookups; i++) {
+ for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) {
+ MOZ_RELEASE_ASSERT(!hs.Search(aVals[j]));
+ }
+ }
+
+ for (size_t i = 0; i < aParams->mNumIterations; i++) {
+ size_t n = 0;
+ for (auto iter = hs.Iter(); !iter.Done(); iter.Next()) {
+ n++;
+ }
+ MOZ_RELEASE_ASSERT(aParams->mNumInserts == n);
+ MOZ_RELEASE_ASSERT(hs.EntryCount() == n);
+ }
+
+ if (aParams->mRemoveInserts) {
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ hs.Remove(aVals[j]);
+ }
+ MOZ_RELEASE_ASSERT(hs.EntryCount() == 0);
+ } else {
+ MOZ_RELEASE_ASSERT(hs.EntryCount() == aParams->mNumInserts);
+ }
+}
+
+// Keep this in sync with all the other Bench_*() functions.
+static void Bench_Cpp_MozHashSet(const Params* aParams, void** aVals,
+ size_t aLen) {
+ mozilla::HashSet<void*, mozilla::DefaultHasher<void*>, MallocAllocPolicy> hs;
+
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ MOZ_RELEASE_ASSERT(hs.put(aVals[j]));
+ }
+
+ for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) {
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ MOZ_RELEASE_ASSERT(hs.has(aVals[j]));
+ }
+ }
+
+ for (size_t i = 0; i < aParams->mNumFailingLookups; i++) {
+ for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) {
+ MOZ_RELEASE_ASSERT(!hs.has(aVals[j]));
+ }
+ }
+
+ for (size_t i = 0; i < aParams->mNumIterations; i++) {
+ size_t n = 0;
+ for (auto iter = hs.iter(); !iter.done(); iter.next()) {
+ n++;
+ }
+ MOZ_RELEASE_ASSERT(aParams->mNumInserts == n);
+ MOZ_RELEASE_ASSERT(hs.count() == n);
+ }
+
+ if (aParams->mRemoveInserts) {
+ for (size_t j = 0; j < aParams->mNumInserts; j++) {
+ hs.remove(aVals[j]);
+ }
+ MOZ_RELEASE_ASSERT(hs.count() == 0);
+ } else {
+ MOZ_RELEASE_ASSERT(hs.count() == aParams->mNumInserts);
+ }
+}
+
+extern "C" {
+void Bench_Rust_HashSet(const Params* params, void** aVals, size_t aLen);
+void Bench_Rust_FnvHashSet(const Params* params, void** aVals, size_t aLen);
+void Bench_Rust_FxHashSet(const Params* params, void** aVals, size_t aLen);
+}
+
+static const size_t VALS_LEN = 131072;
+
+// Each benchmark measures a different aspect of performance.
+// Note that no "Inserts" value can exceed VALS_LEN.
+// Also, if any failing lookups are done, Inserts must be <= VALS_LEN/2.
+const Params gParamsList[] = {
+ // clang-format off
+ // Successful Failing Remove
+ // Inserts lookups lookups Iterations inserts
+ { "succ_lookups", 1024, 5000, 0, 0, false },
+ { "fail_lookups", 1024, 0, 5000, 0, false },
+ { "insert_remove", VALS_LEN, 0, 0, 0, true },
+ { "iterate", 1024, 0, 0, 5000, false },
+ // clang-format on
+};
+
+class BenchCollections : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ StaticMutexAutoLock lock(sValsMutex);
+
+ if (!sVals) {
+ sVals = (void**)malloc(VALS_LEN * sizeof(void*));
+ for (size_t i = 0; i < VALS_LEN; i++) {
+ // This leaves the high 32 bits zero on 64-bit platforms, but that
+ // should still be enough randomness to get typical behaviour.
+ sVals[i] = reinterpret_cast<void*>(uintptr_t(MyRand()));
+ }
+ }
+
+ printf("\n");
+ for (size_t i = 0; i < ArrayLength(gParamsList); i++) {
+ const Params* params = &gParamsList[i];
+ printf("%14s", params->mConfigName);
+ }
+ printf("%14s\n", "total");
+ }
+
+ public:
+ void BenchImpl(void (*aBench)(const Params*, void**, size_t)) {
+ StaticMutexAutoLock lock(sValsMutex);
+
+ double total = 0;
+ for (size_t i = 0; i < ArrayLength(gParamsList); i++) {
+ const Params* params = &gParamsList[i];
+ TimeStamp t1 = TimeStamp::Now();
+ aBench(params, sVals, VALS_LEN);
+ TimeStamp t2 = TimeStamp::Now();
+ double t = (t2 - t1).ToMilliseconds();
+ printf("%11.1f ms", t);
+ total += t;
+ }
+ printf("%11.1f ms\n", total);
+ }
+
+ private:
+ // Random values used in the benchmarks.
+ static void** sVals;
+
+ // A mutex that protects all benchmark operations, ensuring that two
+ // benchmarks never run concurrently.
+ static StaticMutex sValsMutex MOZ_UNANNOTATED;
+};
+
+void** BenchCollections::sVals;
+StaticMutex BenchCollections::sValsMutex;
+
+MOZ_GTEST_BENCH_F(BenchCollections, unordered_set,
+ [this] { BenchImpl(Bench_Cpp_unordered_set); });
+
+MOZ_GTEST_BENCH_F(BenchCollections, PLDHash,
+ [this] { BenchImpl(Bench_Cpp_PLDHashTable); });
+
+MOZ_GTEST_BENCH_F(BenchCollections, MozHash,
+ [this] { BenchImpl(Bench_Cpp_MozHashSet); });
+
+MOZ_GTEST_BENCH_F(BenchCollections, RustHash,
+ [this] { BenchImpl(Bench_Rust_HashSet); });
+
+MOZ_GTEST_BENCH_F(BenchCollections, RustFnvHash,
+ [this] { BenchImpl(Bench_Rust_FnvHashSet); });
+
+MOZ_GTEST_BENCH_F(BenchCollections, RustFxHash,
+ [this] { BenchImpl(Bench_Rust_FxHashSet); });
diff --git a/xpcom/rust/gtest/bench-collections/Cargo.toml b/xpcom/rust/gtest/bench-collections/Cargo.toml
new file mode 100644
index 0000000000..857d87cffd
--- /dev/null
+++ b/xpcom/rust/gtest/bench-collections/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "bench-collections-gtest"
+version = "0.1.0"
+license = "MPL-2.0"
+description = "Benchmarks for various collections"
+
+[dependencies]
+fnv = "1.0"
+fxhash = "0.2.1"
+
+[lib]
+path = "bench.rs"
diff --git a/xpcom/rust/gtest/bench-collections/bench.rs b/xpcom/rust/gtest/bench-collections/bench.rs
new file mode 100644
index 0000000000..1aba69f01f
--- /dev/null
+++ b/xpcom/rust/gtest/bench-collections/bench.rs
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(non_snake_case)]
+
+extern crate fnv;
+extern crate fxhash;
+
+use fnv::FnvHashSet;
+use fxhash::FxHashSet;
+use std::collections::HashSet;
+use std::os::raw::{c_char, c_void};
+use std::slice;
+
+/// Keep this in sync with Params in Bench.cpp.
+#[derive(Debug)]
+#[repr(C)]
+pub struct Params {
+ config_name: *const c_char,
+ num_inserts: usize,
+ num_successful_lookups: usize,
+ num_failing_lookups: usize,
+ num_iterations: usize,
+ remove_inserts: bool,
+}
+
+#[no_mangle]
+pub extern "C" fn Bench_Rust_HashSet(
+ params: *const Params,
+ vals: *const *const c_void,
+ len: usize,
+) {
+ let hs: HashSet<_> = std::collections::HashSet::default();
+ Bench_Rust(hs, params, vals, len);
+}
+
+#[no_mangle]
+pub extern "C" fn Bench_Rust_FnvHashSet(
+ params: *const Params,
+ vals: *const *const c_void,
+ len: usize,
+) {
+ let hs = FnvHashSet::default();
+ Bench_Rust(hs, params, vals, len);
+}
+
+#[no_mangle]
+pub extern "C" fn Bench_Rust_FxHashSet(
+ params: *const Params,
+ vals: *const *const c_void,
+ len: usize,
+) {
+ let hs = FxHashSet::default();
+ Bench_Rust(hs, params, vals, len);
+}
+
+// Keep this in sync with all the other Bench_*() functions.
+fn Bench_Rust<H: std::hash::BuildHasher>(
+ mut hs: HashSet<*const c_void, H>,
+ params: *const Params,
+ vals: *const *const c_void,
+ len: usize,
+) {
+ let params = unsafe { &*params };
+ let vals = unsafe { slice::from_raw_parts(vals, len) };
+
+ for j in 0..params.num_inserts {
+ hs.insert(vals[j]);
+ }
+
+ for _i in 0..params.num_successful_lookups {
+ for j in 0..params.num_inserts {
+ assert!(hs.contains(&vals[j]));
+ }
+ }
+
+ for _i in 0..params.num_failing_lookups {
+ for j in params.num_inserts..params.num_inserts * 2 {
+ assert!(!hs.contains(&vals[j]));
+ }
+ }
+
+ for _i in 0..params.num_iterations {
+ let mut n = 0;
+ for _ in hs.iter() {
+ n += 1;
+ }
+ assert!(params.num_inserts == n);
+ assert!(hs.len() == n);
+ }
+
+ if params.remove_inserts {
+ for j in 0..params.num_inserts {
+ assert!(hs.remove(&vals[j]));
+ }
+ assert!(hs.is_empty());
+ } else {
+ assert!(hs.len() == params.num_inserts);
+ }
+}
diff --git a/xpcom/rust/gtest/moz.build b/xpcom/rust/gtest/moz.build
new file mode 100644
index 0000000000..d3457930c8
--- /dev/null
+++ b/xpcom/rust/gtest/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "bench-collections/Bench.cpp",
+ "moz_task/TestMozTask.cpp",
+ "nsstring/TestnsString.cpp",
+ "xpcom/TestXpcom.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/xpcom/rust/gtest/moz_task/Cargo.toml b/xpcom/rust/gtest/moz_task/Cargo.toml
new file mode 100644
index 0000000000..09d240c309
--- /dev/null
+++ b/xpcom/rust/gtest/moz_task/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "moz_task-gtest"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+license = "MPL-2.0"
+description = "Tests for rust bindings to xpcom event target types"
+edition = "2018"
+
+[dependencies]
+moz_task = { path = "../../moz_task" }
+
+[lib]
+path = "test.rs"
diff --git a/xpcom/rust/gtest/moz_task/TestMozTask.cpp b/xpcom/rust/gtest/moz_task/TestMozTask.cpp
new file mode 100644
index 0000000000..6c40f1bc97
--- /dev/null
+++ b/xpcom/rust/gtest/moz_task/TestMozTask.cpp
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+extern "C" void Rust_Future(bool* aItWorked);
+
+TEST(RustMozTask, Future)
+{
+ bool itWorked = false;
+ Rust_Future(&itWorked);
+ EXPECT_TRUE(itWorked);
+}
diff --git a/xpcom/rust/gtest/moz_task/test.rs b/xpcom/rust/gtest/moz_task/test.rs
new file mode 100644
index 0000000000..fa4c0af852
--- /dev/null
+++ b/xpcom/rust/gtest/moz_task/test.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use moz_task;
+use std::{
+ future::Future,
+ pin::Pin,
+ task::{Context, Poll, Waker},
+};
+
+/// Demo `Future` to demonstrate executing futures to completion via `nsIEventTarget`.
+struct MyFuture {
+ poll_count: u32,
+ waker: Option<Waker>,
+ expect_main_thread: bool,
+}
+
+impl MyFuture {
+ fn new(expect_main_thread: bool) -> Self {
+ Self {
+ poll_count: 0,
+ waker: None,
+ expect_main_thread,
+ }
+ }
+}
+
+impl Future for MyFuture {
+ type Output = u32;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> {
+ assert_eq!(moz_task::is_main_thread(), self.expect_main_thread);
+
+ self.poll_count += 1;
+
+ if let Some(waker) = &mut self.waker {
+ if !waker.will_wake(cx.waker()) {
+ *waker = cx.waker().clone();
+ }
+ } else {
+ let waker = cx.waker().clone();
+ self.waker = Some(waker);
+ }
+
+ println!("Poll count = {}", self.poll_count);
+ if self.poll_count > 5 {
+ Poll::Ready(self.poll_count)
+ } else {
+ // Just notify the task that we need to re-polled.
+ if let Some(waker) = &self.waker {
+ waker.wake_by_ref();
+ }
+ Poll::Pending
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_Future(it_worked: *mut bool) {
+ let future = async move {
+ assert_eq!(MyFuture::new(true).await, 6);
+ assert_eq!(
+ moz_task::spawn_local("Rust_Future inner spawn_local", MyFuture::new(true)).await,
+ 6
+ );
+ assert_eq!(
+ moz_task::spawn("Rust_Future inner spawn", MyFuture::new(false)).await,
+ 6
+ );
+ unsafe {
+ *it_worked = true;
+ }
+ };
+ unsafe {
+ moz_task::gtest_only::spin_event_loop_until("Rust_Future", future).unwrap();
+ };
+}
diff --git a/xpcom/rust/gtest/nsstring/Cargo.toml b/xpcom/rust/gtest/nsstring/Cargo.toml
new file mode 100644
index 0000000000..fd997926a7
--- /dev/null
+++ b/xpcom/rust/gtest/nsstring/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "nsstring-gtest"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+license = "MPL-2.0"
+description = "Tests for rust bindings to xpcom string types"
+
+[dependencies]
+nsstring = { path = "../../nsstring" }
+
+[lib]
+path = "test.rs"
diff --git a/xpcom/rust/gtest/nsstring/TestnsString.cpp b/xpcom/rust/gtest/nsstring/TestnsString.cpp
new file mode 100644
index 0000000000..4e14c7bca5
--- /dev/null
+++ b/xpcom/rust/gtest/nsstring/TestnsString.cpp
@@ -0,0 +1,177 @@
+#include "gtest/gtest.h"
+#include <stdint.h>
+#include <utility>
+#include "nsString.h"
+
+extern "C" {
+// This function is called by the rust code in test.rs if a non-fatal test
+// failure occurs.
+void GTest_ExpectFailure(const char* aMessage) { EXPECT_STREQ(aMessage, ""); }
+}
+
+#define SIZE_ALIGN_CHECK(Clazz) \
+ extern "C" void Rust_Test_ReprSizeAlign_##Clazz(size_t* size, \
+ size_t* align); \
+ TEST(RustNsString, ReprSizeAlign_##Clazz) \
+ { \
+ size_t size, align; \
+ Rust_Test_ReprSizeAlign_##Clazz(&size, &align); \
+ EXPECT_EQ(size, sizeof(Clazz)); \
+ EXPECT_EQ(align, alignof(Clazz)); \
+ }
+
+SIZE_ALIGN_CHECK(nsString)
+SIZE_ALIGN_CHECK(nsCString)
+
+#define MEMBER_CHECK(Clazz, Member) \
+ extern "C" void Rust_Test_Member_##Clazz##_##Member( \
+ size_t* size, size_t* align, size_t* offset); \
+ TEST(RustNsString, ReprMember_##Clazz##_##Member) \
+ { \
+ class Hack : public Clazz { \
+ public: \
+ static void RunTest() { \
+ size_t size, align, offset; \
+ Rust_Test_Member_##Clazz##_##Member(&size, &align, &offset); \
+ EXPECT_EQ(size, sizeof(std::declval<Hack>().Member)); \
+ EXPECT_EQ(align, alignof(decltype(std::declval<Hack>().Member))); \
+ EXPECT_EQ(offset, offsetof(Hack, Member)); \
+ } \
+ }; \
+ static_assert(sizeof(Clazz) == sizeof(Hack), "Hack matches class"); \
+ Hack::RunTest(); \
+ }
+
+MEMBER_CHECK(nsString, mData)
+MEMBER_CHECK(nsString, mLength)
+MEMBER_CHECK(nsString, mDataFlags)
+MEMBER_CHECK(nsString, mClassFlags)
+MEMBER_CHECK(nsCString, mData)
+MEMBER_CHECK(nsCString, mLength)
+MEMBER_CHECK(nsCString, mDataFlags)
+MEMBER_CHECK(nsCString, mClassFlags)
+
+extern "C" void Rust_Test_NsStringFlags(
+ uint16_t* f_terminated, uint16_t* f_voided, uint16_t* f_refcounted,
+ uint16_t* f_owned, uint16_t* f_inline, uint16_t* f_literal,
+ uint16_t* f_class_inline, uint16_t* f_class_null_terminated);
+TEST(RustNsString, NsStringFlags)
+{
+ uint16_t f_terminated, f_voided, f_refcounted, f_owned, f_inline, f_literal,
+ f_class_inline, f_class_null_terminated;
+ Rust_Test_NsStringFlags(&f_terminated, &f_voided, &f_refcounted, &f_owned,
+ &f_inline, &f_literal, &f_class_inline,
+ &f_class_null_terminated);
+ EXPECT_EQ(f_terminated, uint16_t(nsAString::DataFlags::TERMINATED));
+ EXPECT_EQ(f_terminated, uint16_t(nsACString::DataFlags::TERMINATED));
+ EXPECT_EQ(f_voided, uint16_t(nsAString::DataFlags::VOIDED));
+ EXPECT_EQ(f_voided, uint16_t(nsACString::DataFlags::VOIDED));
+ EXPECT_EQ(f_refcounted, uint16_t(nsAString::DataFlags::REFCOUNTED));
+ EXPECT_EQ(f_refcounted, uint16_t(nsACString::DataFlags::REFCOUNTED));
+ EXPECT_EQ(f_owned, uint16_t(nsAString::DataFlags::OWNED));
+ EXPECT_EQ(f_owned, uint16_t(nsACString::DataFlags::OWNED));
+ EXPECT_EQ(f_inline, uint16_t(nsAString::DataFlags::INLINE));
+ EXPECT_EQ(f_inline, uint16_t(nsACString::DataFlags::INLINE));
+ EXPECT_EQ(f_literal, uint16_t(nsAString::DataFlags::LITERAL));
+ EXPECT_EQ(f_literal, uint16_t(nsACString::DataFlags::LITERAL));
+ EXPECT_EQ(f_class_inline, uint16_t(nsAString::ClassFlags::INLINE));
+ EXPECT_EQ(f_class_inline, uint16_t(nsACString::ClassFlags::INLINE));
+ EXPECT_EQ(f_class_null_terminated,
+ uint16_t(nsAString::ClassFlags::NULL_TERMINATED));
+ EXPECT_EQ(f_class_null_terminated,
+ uint16_t(nsACString::ClassFlags::NULL_TERMINATED));
+}
+
+extern "C" void Rust_StringFromCpp(const nsACString* aCStr,
+ const nsAString* aStr);
+TEST(RustNsString, StringFromCpp)
+{
+ nsAutoCString foo;
+ foo.AssignASCII("Hello, World!");
+
+ nsAutoString bar;
+ bar.AssignASCII("Hello, World!");
+
+ Rust_StringFromCpp(&foo, &bar);
+}
+
+extern "C" void Rust_AssignFromRust(nsACString* aCStr, nsAString* aStr);
+TEST(RustNsString, AssignFromRust)
+{
+ nsAutoCString cs;
+ nsAutoString s;
+ Rust_AssignFromRust(&cs, &s);
+ EXPECT_TRUE(cs.EqualsASCII("Hello, World!"));
+ EXPECT_TRUE(s.EqualsASCII("Hello, World!"));
+}
+
+extern "C" {
+void Cpp_AssignFromCpp(nsACString* aCStr, nsAString* aStr) {
+ aCStr->AssignASCII("Hello, World!");
+ aStr->AssignASCII("Hello, World!");
+}
+}
+extern "C" void Rust_AssignFromCpp();
+TEST(RustNsString, AssignFromCpp)
+{ Rust_AssignFromCpp(); }
+
+extern "C" void Rust_StringWrite();
+TEST(RustNsString, StringWrite)
+{ Rust_StringWrite(); }
+
+extern "C" void Rust_FromEmptyRustString();
+TEST(RustNsString, FromEmptyRustString)
+{ Rust_FromEmptyRustString(); }
+
+extern "C" void Rust_WriteToBufferFromRust(nsACString* aCStr, nsAString* aStr,
+ nsACString* aFallibleCStr,
+ nsAString* aFallibleStr);
+TEST(RustNsString, WriteToBufferFromRust)
+{
+ nsAutoCString cStr;
+ nsAutoString str;
+ nsAutoCString fallibleCStr;
+ nsAutoString fallibleStr;
+
+ cStr.AssignLiteral("abc");
+ str.AssignLiteral("abc");
+ fallibleCStr.AssignLiteral("abc");
+ fallibleStr.AssignLiteral("abc");
+
+ Rust_WriteToBufferFromRust(&cStr, &str, &fallibleCStr, &fallibleStr);
+
+ EXPECT_TRUE(cStr.EqualsASCII("ABC"));
+ EXPECT_TRUE(str.EqualsASCII("ABC"));
+ EXPECT_TRUE(fallibleCStr.EqualsASCII("ABC"));
+ EXPECT_TRUE(fallibleStr.EqualsASCII("ABC"));
+}
+
+extern "C" void Rust_InlineCapacityFromRust(const nsACString* aCStr,
+ const nsAString* aStr,
+ size_t* aCStrCapacity,
+ size_t* aStrCapacity);
+TEST(RustNsString, InlineCapacityFromRust)
+{
+ size_t cStrCapacity;
+ size_t strCapacity;
+ nsAutoCStringN<93> cs;
+ nsAutoStringN<93> s;
+ Rust_InlineCapacityFromRust(&cs, &s, &cStrCapacity, &strCapacity);
+ EXPECT_EQ(cStrCapacity, 92U);
+ EXPECT_EQ(strCapacity, 92U);
+}
+
+extern "C" void Rust_VoidStringFromRust(nsACString* aCStr, nsAString* aStr);
+TEST(RustNsString, VoidStringFromRust)
+{
+ nsAutoCString cs;
+ nsAutoString s;
+
+ EXPECT_FALSE(cs.IsVoid());
+ EXPECT_FALSE(s.IsVoid());
+
+ Rust_VoidStringFromRust(&cs, &s);
+
+ EXPECT_TRUE(cs.IsVoid());
+ EXPECT_TRUE(s.IsVoid());
+}
diff --git a/xpcom/rust/gtest/nsstring/test.rs b/xpcom/rust/gtest/nsstring/test.rs
new file mode 100644
index 0000000000..a5d142f2b2
--- /dev/null
+++ b/xpcom/rust/gtest/nsstring/test.rs
@@ -0,0 +1,131 @@
+#![allow(non_snake_case)]
+
+extern crate nsstring;
+
+use nsstring::*;
+use std::ffi::CString;
+use std::fmt::Write;
+use std::os::raw::c_char;
+
+fn nonfatal_fail(msg: String) {
+ extern "C" {
+ fn GTest_ExpectFailure(message: *const c_char);
+ }
+ unsafe {
+ let msg = CString::new(msg).unwrap();
+ GTest_ExpectFailure(msg.as_ptr());
+ }
+}
+
+/// This macro checks if the two arguments are equal, and causes a non-fatal
+/// GTest test failure if they are not.
+macro_rules! expect_eq {
+ ($x:expr, $y:expr) => {
+ match (&$x, &$y) {
+ (x, y) => {
+ if *x != *y {
+ nonfatal_fail(format!(
+ "check failed: (`{:?}` == `{:?}`) at {}:{}",
+ x,
+ y,
+ file!(),
+ line!()
+ ))
+ }
+ }
+ }
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_StringFromCpp(cs: *const nsACString, s: *const nsAString) {
+ unsafe {
+ expect_eq!(&*cs, "Hello, World!");
+ expect_eq!(&*s, "Hello, World!");
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_AssignFromRust(cs: *mut nsACString, s: *mut nsAString) {
+ unsafe {
+ (*cs).assign(&nsCString::from("Hello, World!"));
+ expect_eq!(&*cs, "Hello, World!");
+ (*s).assign(&nsString::from("Hello, World!"));
+ expect_eq!(&*s, "Hello, World!");
+ }
+}
+
+extern "C" {
+ fn Cpp_AssignFromCpp(cs: *mut nsACString, s: *mut nsAString);
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_AssignFromCpp() {
+ let mut cs = nsCString::new();
+ let mut s = nsString::new();
+ unsafe {
+ Cpp_AssignFromCpp(&mut *cs, &mut *s);
+ }
+ expect_eq!(cs, "Hello, World!");
+ expect_eq!(s, "Hello, World!");
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_StringWrite() {
+ let mut cs = nsCString::new();
+ let mut s = nsString::new();
+
+ write!(s, "a").unwrap();
+ write!(cs, "a").unwrap();
+ expect_eq!(s, "a");
+ expect_eq!(cs, "a");
+ write!(s, "bc").unwrap();
+ write!(cs, "bc").unwrap();
+ expect_eq!(s, "abc");
+ expect_eq!(cs, "abc");
+ write!(s, "{}", 123).unwrap();
+ write!(cs, "{}", 123).unwrap();
+ expect_eq!(s, "abc123");
+ expect_eq!(cs, "abc123");
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_FromEmptyRustString() {
+ let mut test = nsString::from("Blah");
+ test.assign_utf8(&nsCString::from(String::new()));
+ assert!(test.is_empty());
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_WriteToBufferFromRust(
+ cs: *mut nsACString,
+ s: *mut nsAString,
+ fallible_cs: *mut nsACString,
+ fallible_s: *mut nsAString,
+) {
+ unsafe {
+ let cs_buf = (*cs).to_mut();
+ let s_buf = (*s).to_mut();
+ let fallible_cs_buf = (*fallible_cs).fallible_to_mut().unwrap();
+ let fallible_s_buf = (*fallible_s).fallible_to_mut().unwrap();
+
+ cs_buf[0] = b'A';
+ cs_buf[1] = b'B';
+ cs_buf[2] = b'C';
+ s_buf[0] = b'A' as u16;
+ s_buf[1] = b'B' as u16;
+ s_buf[2] = b'C' as u16;
+ fallible_cs_buf[0] = b'A';
+ fallible_cs_buf[1] = b'B';
+ fallible_cs_buf[2] = b'C';
+ fallible_s_buf[0] = b'A' as u16;
+ fallible_s_buf[1] = b'B' as u16;
+ fallible_s_buf[2] = b'C' as u16;
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_VoidStringFromRust(cs: &mut nsACString, s: &mut nsAString) {
+ cs.set_is_void(true);
+ s.set_is_void(true);
+}
diff --git a/xpcom/rust/gtest/xpcom/Cargo.toml b/xpcom/rust/gtest/xpcom/Cargo.toml
new file mode 100644
index 0000000000..777080b33b
--- /dev/null
+++ b/xpcom/rust/gtest/xpcom/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "xpcom-gtest"
+version = "0.1.0"
+authors = ["michael@thelayzells.com"]
+license = "MPL-2.0"
+description = "Tests for rust bindings to xpcom interfaces"
+
+[dependencies]
+xpcom = { path = "../../xpcom" }
+nserror = { path = "../../nserror" }
+nsstring = { path = "../../nsstring" }
+
+[lib]
+path = "test.rs"
diff --git a/xpcom/rust/gtest/xpcom/TestXpcom.cpp b/xpcom/rust/gtest/xpcom/TestXpcom.cpp
new file mode 100644
index 0000000000..69425274f6
--- /dev/null
+++ b/xpcom/rust/gtest/xpcom/TestXpcom.cpp
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "nsCOMPtr.h"
+#include "nsIRunnable.h"
+#include "nsIObserver.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+
+extern "C" nsIObserverService* Rust_ObserveFromRust();
+
+TEST(RustXpcom, ObserverFromRust)
+{
+ nsCOMPtr<nsIObserverService> rust = Rust_ObserveFromRust();
+ nsCOMPtr<nsIObserverService> cpp = mozilla::services::GetObserverService();
+ EXPECT_EQ(rust, cpp);
+}
+
+extern "C" void Rust_ImplementRunnableInRust(bool* aItWorked,
+ nsIRunnable** aRunnable);
+
+TEST(RustXpcom, ImplementRunnableInRust)
+{
+ bool itWorked = false;
+ nsCOMPtr<nsIRunnable> runnable;
+ Rust_ImplementRunnableInRust(&itWorked, getter_AddRefs(runnable));
+
+ EXPECT_TRUE(runnable);
+ EXPECT_FALSE(itWorked);
+ runnable->Run();
+ EXPECT_TRUE(itWorked);
+}
+
+extern "C" void Rust_GetMultipleInterfaces(nsIRunnable** aRunnable,
+ nsIObserver** aObserver);
+
+TEST(RustXpcom, DynamicCastVoid)
+{
+ nsCOMPtr<nsIRunnable> runnable;
+ nsCOMPtr<nsIObserver> observer;
+ Rust_GetMultipleInterfaces(getter_AddRefs(runnable),
+ getter_AddRefs(observer));
+
+ // They should have different addresses when `static_cast` to void*
+ EXPECT_NE(static_cast<void*>(runnable.get()),
+ static_cast<void*>(observer.get()));
+
+ // These should be the same object
+ nsCOMPtr<nsISupports> runnableSupports = do_QueryInterface(runnable);
+ nsCOMPtr<nsISupports> observerSupports = do_QueryInterface(observer);
+ EXPECT_EQ(runnableSupports.get(), observerSupports.get());
+
+#ifndef XP_WIN
+ // They should have the same address when dynamic_cast to void*
+ // dynamic_cast<void*> is not supported without rtti on windows.
+ EXPECT_EQ(dynamic_cast<void*>(runnable.get()),
+ dynamic_cast<void*>(observer.get()));
+
+ // The nsISupports pointer from `do_QueryInterface` should match
+ // `dynamic_cast<void*>`
+ EXPECT_EQ(dynamic_cast<void*>(observer.get()),
+ static_cast<void*>(observerSupports.get()));
+#endif
+}
diff --git a/xpcom/rust/gtest/xpcom/test.rs b/xpcom/rust/gtest/xpcom/test.rs
new file mode 100644
index 0000000000..f26a0140f3
--- /dev/null
+++ b/xpcom/rust/gtest/xpcom/test.rs
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(non_snake_case)]
+
+#[macro_use]
+extern crate xpcom;
+
+extern crate nserror;
+
+use nserror::{nsresult, NS_OK};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use std::ptr;
+use xpcom::{interfaces, RefPtr};
+
+#[no_mangle]
+pub unsafe extern "C" fn Rust_ObserveFromRust() -> *const interfaces::nsIObserverService {
+ let obssvc: RefPtr<interfaces::nsIObserverService> =
+ xpcom::components::Observer::service().unwrap();
+
+ // Define an observer
+ #[xpcom(implement(nsIObserver), nonatomic)]
+ struct Observer {
+ run: *mut bool,
+ }
+ impl Observer {
+ unsafe fn Observe(
+ &self,
+ _subject: *const interfaces::nsISupports,
+ topic: *const c_char,
+ _data: *const u16,
+ ) -> nsresult {
+ *self.run = true;
+ assert!(CStr::from_ptr(topic).to_str() == Ok("test-rust-observe"));
+ NS_OK
+ }
+ }
+
+ let topic = CString::new("test-rust-observe").unwrap();
+
+ let mut run = false;
+ let observer = Observer::allocate(InitObserver { run: &mut run });
+ let rv = obssvc.AddObserver(
+ observer.coerce::<interfaces::nsIObserver>(),
+ topic.as_ptr(),
+ false,
+ );
+ assert!(rv.succeeded());
+
+ let rv = obssvc.NotifyObservers(ptr::null(), topic.as_ptr(), ptr::null());
+ assert!(rv.succeeded());
+ assert!(run, "The observer should have been run!");
+
+ let rv = obssvc.RemoveObserver(observer.coerce::<interfaces::nsIObserver>(), topic.as_ptr());
+ assert!(rv.succeeded());
+
+ assert!(
+ observer.coerce::<interfaces::nsISupports>() as *const _
+ == &*observer
+ .query_interface::<interfaces::nsISupports>()
+ .unwrap() as *const _
+ );
+
+ &*obssvc
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn Rust_ImplementRunnableInRust(
+ it_worked: *mut bool,
+ runnable: *mut *const interfaces::nsIRunnable,
+) {
+ // Define a type which implements nsIRunnable in rust.
+ #[xpcom(implement(nsIRunnable), atomic)]
+ struct RunnableFn<F: Fn() + 'static> {
+ run: F,
+ }
+
+ impl<F: Fn() + 'static> RunnableFn<F> {
+ unsafe fn Run(&self) -> nsresult {
+ (self.run)();
+ NS_OK
+ }
+ }
+
+ let my_runnable = RunnableFn::allocate(InitRunnableFn {
+ run: move || {
+ *it_worked = true;
+ },
+ });
+ my_runnable
+ .query_interface::<interfaces::nsIRunnable>()
+ .unwrap()
+ .forget(&mut *runnable);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn Rust_GetMultipleInterfaces(
+ runnable: *mut *const interfaces::nsIRunnable,
+ observer: *mut *const interfaces::nsIObserver,
+) {
+ // Define a type which implements nsIRunnable and nsIObserver in rust, and
+ // hand both references back to c++
+ #[xpcom(implement(nsIRunnable, nsIObserver), atomic)]
+ struct MultipleInterfaces {}
+
+ impl MultipleInterfaces {
+ unsafe fn Run(&self) -> nsresult {
+ NS_OK
+ }
+ unsafe fn Observe(
+ &self,
+ _subject: *const interfaces::nsISupports,
+ _topic: *const c_char,
+ _data: *const u16,
+ ) -> nsresult {
+ NS_OK
+ }
+ }
+
+ let instance = MultipleInterfaces::allocate(InitMultipleInterfaces {});
+ instance
+ .query_interface::<interfaces::nsIRunnable>()
+ .unwrap()
+ .forget(&mut *runnable);
+ instance
+ .query_interface::<interfaces::nsIObserver>()
+ .unwrap()
+ .forget(&mut *observer);
+}
diff --git a/xpcom/rust/moz_task/Cargo.toml b/xpcom/rust/moz_task/Cargo.toml
new file mode 100644
index 0000000000..2e3e43ef7a
--- /dev/null
+++ b/xpcom/rust/moz_task/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "moz_task"
+version = "0.1.0"
+authors = ["Myk Melez <myk@mykzilla.org>"]
+license = "MPL-2.0"
+description = "Rust wrappers around XPCOM threading functions"
+edition = "2018"
+
+[dependencies]
+log = "0.4"
+cstr = "0.2"
+libc = "0.2"
+async-task = { version = "4.3" }
+nserror = { path = "../nserror" }
+nsstring = { path = "../nsstring" }
+xpcom = { path = "../xpcom" }
diff --git a/xpcom/rust/moz_task/src/dispatcher.rs b/xpcom/rust/moz_task/src/dispatcher.rs
new file mode 100644
index 0000000000..17ad9ceb81
--- /dev/null
+++ b/xpcom/rust/moz_task/src/dispatcher.rs
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ dispatch_background_task_runnable, dispatch_runnable, get_current_thread, DispatchOptions,
+};
+use nserror::{nsresult, NS_OK};
+use nsstring::nsACString;
+use std::sync::Mutex;
+use xpcom::interfaces::{nsIEventTarget, nsIRunnablePriority};
+use xpcom::xpcom;
+
+/// Basic wrapper to convert a FnOnce callback into a `nsIRunnable` to be
+/// dispatched using XPCOM.
+#[xpcom(implement(nsIRunnable, nsINamed, nsIRunnablePriority), atomic)]
+struct RunnableFunction<F: FnOnce() + 'static> {
+ name: &'static str,
+ priority: u32,
+ function: Mutex<Option<F>>,
+}
+
+impl<F: FnOnce() + 'static> RunnableFunction<F> {
+ #[allow(non_snake_case)]
+ fn Run(&self) -> nsresult {
+ let function = self.function.lock().unwrap().take();
+ debug_assert!(function.is_some(), "runnable invoked twice?");
+ if let Some(function) = function {
+ function();
+ }
+ NS_OK
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn GetName(&self, result: *mut nsACString) -> nsresult {
+ (*result).assign(self.name);
+ NS_OK
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn GetPriority(&self, result: *mut u32) -> nsresult {
+ *result = self.priority;
+ NS_OK
+ }
+}
+
+pub struct RunnableBuilder<F> {
+ name: &'static str,
+ function: F,
+ priority: u32,
+ options: DispatchOptions,
+}
+
+impl<F> RunnableBuilder<F> {
+ pub fn new(name: &'static str, function: F) -> Self {
+ RunnableBuilder {
+ name,
+ function,
+ priority: nsIRunnablePriority::PRIORITY_NORMAL,
+ options: DispatchOptions::default(),
+ }
+ }
+
+ pub fn priority(mut self, priority: u32) -> Self {
+ self.priority = priority;
+ self
+ }
+
+ pub fn options(mut self, options: DispatchOptions) -> Self {
+ self.options = options;
+ self
+ }
+
+ pub fn may_block(mut self, may_block: bool) -> Self {
+ self.options = self.options.may_block(may_block);
+ self
+ }
+
+ pub unsafe fn at_end(mut self, at_end: bool) -> Self {
+ self.options = self.options.at_end(at_end);
+ self
+ }
+}
+
+impl<F> RunnableBuilder<F>
+where
+ F: FnOnce() + Send + 'static,
+{
+ /// Dispatch this Runnable to the specified EventTarget. The runnable function must be `Send`.
+ pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), nsresult> {
+ let runnable = RunnableFunction::allocate(InitRunnableFunction {
+ name: self.name,
+ priority: self.priority,
+ function: Mutex::new(Some(self.function)),
+ });
+ unsafe { dispatch_runnable(runnable.coerce(), target, self.options) }
+ }
+
+ /// Dispatch this Runnable to the specified EventTarget as a background
+ /// task. The runnable function must be `Send`.
+ pub fn dispatch_background_task(self) -> Result<(), nsresult> {
+ let runnable = RunnableFunction::allocate(InitRunnableFunction {
+ name: self.name,
+ priority: self.priority,
+ function: Mutex::new(Some(self.function)),
+ });
+ unsafe { dispatch_background_task_runnable(runnable.coerce(), self.options) }
+ }
+}
+
+impl<F> RunnableBuilder<F>
+where
+ F: FnOnce() + 'static,
+{
+ /// Dispatch this Runnable to the current thread.
+ ///
+ /// Unlike `dispatch` and `dispatch_background_task`, the runnable does not
+ /// need to be `Send` to dispatch to the current thread.
+ pub fn dispatch_local(self) -> Result<(), nsresult> {
+ let target = get_current_thread()?;
+ let runnable = RunnableFunction::allocate(InitRunnableFunction {
+ name: self.name,
+ priority: self.priority,
+ function: Mutex::new(Some(self.function)),
+ });
+ unsafe { dispatch_runnable(runnable.coerce(), target.coerce(), self.options) }
+ }
+}
+
+pub fn dispatch_onto<F>(
+ name: &'static str,
+ target: &nsIEventTarget,
+ function: F,
+) -> Result<(), nsresult>
+where
+ F: FnOnce() + Send + 'static,
+{
+ RunnableBuilder::new(name, function).dispatch(target)
+}
+
+pub fn dispatch_background_task<F>(name: &'static str, function: F) -> Result<(), nsresult>
+where
+ F: FnOnce() + Send + 'static,
+{
+ RunnableBuilder::new(name, function).dispatch_background_task()
+}
+
+pub fn dispatch_local<F>(name: &'static str, function: F) -> Result<(), nsresult>
+where
+ F: FnOnce() + 'static,
+{
+ RunnableBuilder::new(name, function).dispatch_local()
+}
diff --git a/xpcom/rust/moz_task/src/event_loop.rs b/xpcom/rust/moz_task/src/event_loop.rs
new file mode 100644
index 0000000000..f8d113ed57
--- /dev/null
+++ b/xpcom/rust/moz_task/src/event_loop.rs
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nsstring;
+
+use cstr::cstr;
+use nserror::{nsresult, NS_ERROR_SERVICE_NOT_AVAILABLE, NS_ERROR_UNEXPECTED, NS_OK};
+use nsstring::*;
+use std::cell::RefCell;
+use std::future::Future;
+use xpcom::{interfaces::nsIThreadManager, xpcom, xpcom_method};
+
+#[xpcom(implement(nsINestedEventLoopCondition), nonatomic)]
+struct FutureCompleteCondition<T: 'static> {
+ value: RefCell<Option<T>>,
+}
+
+impl<T: 'static> FutureCompleteCondition<T> {
+ xpcom_method!(is_done => IsDone() -> bool);
+ fn is_done(&self) -> Result<bool, nsresult> {
+ Ok(self.value.borrow().is_some())
+ }
+}
+
+/// Spin the event loop on the current thread until `future` is resolved.
+///
+/// # Safety
+///
+/// Spinning a nested event loop should always be avoided when possible, as it
+/// can cause hangs, break JS run-to-completion guarantees, and break other C++
+/// code currently on the stack relying on heap invariants. While in a pure-rust
+/// codebase this method would only be ill-advised and not technically "unsafe",
+/// it is marked as unsafe due to the potential for triggering unsafety in
+/// unrelated C++ code.
+pub unsafe fn spin_event_loop_until<F>(
+ reason: &'static str,
+ future: F,
+) -> Result<F::Output, nsresult>
+where
+ F: Future + 'static,
+ F::Output: 'static,
+{
+ let thread_manager =
+ xpcom::get_service::<nsIThreadManager>(cstr!("@mozilla.org/thread-manager;1"))
+ .ok_or(NS_ERROR_SERVICE_NOT_AVAILABLE)?;
+
+ let cond = FutureCompleteCondition::<F::Output>::allocate(InitFutureCompleteCondition {
+ value: RefCell::new(None),
+ });
+
+ // Spawn our future onto the current thread event loop, and record the
+ // completed value as it completes.
+ let cond2 = cond.clone();
+ crate::spawn_local(reason, async move {
+ let rv = future.await;
+ *cond2.value.borrow_mut() = Some(rv);
+ })
+ .detach();
+
+ thread_manager
+ .SpinEventLoopUntil(&*nsCStr::from(reason), cond.coerce())
+ .to_result()?;
+ let rv = cond.value.borrow_mut().take();
+ rv.ok_or(NS_ERROR_UNEXPECTED)
+}
diff --git a/xpcom/rust/moz_task/src/executor.rs b/xpcom/rust/moz_task/src/executor.rs
new file mode 100644
index 0000000000..0016839373
--- /dev/null
+++ b/xpcom/rust/moz_task/src/executor.rs
@@ -0,0 +1,291 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{get_current_thread, DispatchOptions, RunnableBuilder};
+use std::{
+ cell::Cell,
+ fmt::Debug,
+ future::Future,
+ pin::Pin,
+ ptr,
+ sync::Arc,
+ task::{Context, Poll},
+};
+use xpcom::interfaces::{nsIEventTarget, nsIRunnablePriority};
+use xpcom::RefPtr;
+
+/// A spawned task.
+///
+/// A [`AsyncTask`] can be awaited to retrieve the output of its future.
+///
+/// Dropping an [`AsyncTask`] cancels it, which means its future won't be polled
+/// again. To drop the [`AsyncTask`] handle without canceling it, use
+/// [`detach()`][`AsyncTask::detach()`] instead. To cancel a task gracefully and
+/// wait until it is fully destroyed, use the [`cancel()`][AsyncTask::cancel()]
+/// method.
+///
+/// A task which is cancelled due to the nsIEventTarget it was dispatched to no
+/// longer accepting events will never be resolved.
+#[derive(Debug)]
+#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
+pub struct AsyncTask<T> {
+ task: async_task::FallibleTask<T>,
+}
+
+impl<T> AsyncTask<T> {
+ fn new(task: async_task::Task<T>) -> Self {
+ AsyncTask {
+ task: task.fallible(),
+ }
+ }
+
+ /// Detaches the task to let it keep running in the background.
+ pub fn detach(self) {
+ self.task.detach()
+ }
+
+ /// Cancels the task and waits for it to stop running.
+ ///
+ /// Returns the task's output if it was completed just before it got canceled, or [`None`] if
+ /// it didn't complete.
+ ///
+ /// While it's possible to simply drop the [`Task`] to cancel it, this is a cleaner way of
+ /// canceling because it also waits for the task to stop running.
+ pub async fn cancel(self) -> Option<T> {
+ self.task.cancel().await
+ }
+}
+
+impl<T> Future for AsyncTask<T> {
+ type Output = T;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ // Wrap the future produced by `AsyncTask` to never resolve if the
+ // Runnable was dropped, and the task was cancelled.
+ match Pin::new(&mut self.task).poll(cx) {
+ Poll::Ready(Some(t)) => Poll::Ready(t),
+ Poll::Ready(None) | Poll::Pending => Poll::Pending,
+ }
+ }
+}
+
+enum SpawnTarget {
+ BackgroundTask,
+ EventTarget(RefPtr<nsIEventTarget>),
+}
+
+// SAFETY: All XPCOM interfaces are considered !Send + !Sync, however all
+// well-behaved nsIEventTarget instances must be threadsafe.
+unsafe impl Send for SpawnTarget {}
+unsafe impl Sync for SpawnTarget {}
+
+/// Information used by tasks as they are spawned. Stored in an Arc such that
+/// their identity can be used for `POLLING_TASK`.
+struct TaskSpawnConfig {
+ name: &'static str,
+ priority: u32,
+ options: DispatchOptions,
+ target: SpawnTarget,
+}
+
+thread_local! {
+ /// Raw pointer to the TaskSpawnConfig for the currently polling task. Used
+ /// to detect scheduling callbacks for a runnable while it is polled, to set
+ /// `DISPATCH_AT_END` on the notification.
+ static POLLING_TASK: Cell<*const TaskSpawnConfig> = Cell::new(ptr::null());
+}
+
+fn schedule(config: Arc<TaskSpawnConfig>, runnable: async_task::Runnable) {
+ // If we're dispatching this task while it is currently running on the same
+ // thread, set the `DISPATCH_AT_END` flag in the dispatch options to tell
+ // our threadpool target to not bother to spin up another thread.
+ let currently_polling = POLLING_TASK.with(|t| t.get() == Arc::as_ptr(&config));
+
+ // SAFETY: We use the POLLING_TASK thread local to check if we meet the
+ // requirements for `at_end`.
+ let options = unsafe { config.options.at_end(currently_polling) };
+
+ // Build the RunnableBuilder for our task to be dispatched.
+ let config2 = config.clone();
+ let builder = RunnableBuilder::new(config.name, move || {
+ // Record the pointer for the currently executing task in the
+ // POLLING_TASK thread-local so that nested dispatches can detect it.
+ POLLING_TASK.with(|t| {
+ let prev = t.get();
+ t.set(Arc::as_ptr(&config2));
+ runnable.run();
+ t.set(prev);
+ });
+ })
+ .priority(config.priority)
+ .options(options);
+
+ let rv = match &config.target {
+ SpawnTarget::BackgroundTask => builder.dispatch_background_task(),
+ SpawnTarget::EventTarget(target) => builder.dispatch(&*target),
+ };
+ if let Err(err) = rv {
+ log::warn!(
+ "dispatch for spawned task '{}' failed: {:?}",
+ config.name,
+ err
+ );
+ }
+}
+
+/// Helper for starting an async task which will run a future to completion.
+#[derive(Debug)]
+pub struct TaskBuilder<F> {
+ name: &'static str,
+ future: F,
+ priority: u32,
+ options: DispatchOptions,
+}
+
+impl<F> TaskBuilder<F> {
+ pub fn new(name: &'static str, future: F) -> TaskBuilder<F> {
+ TaskBuilder {
+ name,
+ future,
+ priority: nsIRunnablePriority::PRIORITY_NORMAL,
+ options: DispatchOptions::default(),
+ }
+ }
+
+ /// Specify the priority of the task's runnables.
+ pub fn priority(mut self, priority: u32) -> Self {
+ self.priority = priority;
+ self
+ }
+
+ /// Specify options to use when dispatching the task.
+ pub fn options(mut self, options: DispatchOptions) -> Self {
+ self.options = options;
+ self
+ }
+
+ /// Set whether or not the event may block, and should be run on the IO
+ /// thread pool.
+ pub fn may_block(mut self, may_block: bool) -> Self {
+ self.options = self.options.may_block(may_block);
+ self
+ }
+}
+
+impl<F> TaskBuilder<F>
+where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+{
+ /// Run the future on the background task pool.
+ pub fn spawn(self) -> AsyncTask<F::Output> {
+ let config = Arc::new(TaskSpawnConfig {
+ name: self.name,
+ priority: self.priority,
+ options: self.options,
+ target: SpawnTarget::BackgroundTask,
+ });
+ let (runnable, task) = async_task::spawn(self.future, move |runnable| {
+ schedule(config.clone(), runnable)
+ });
+ runnable.schedule();
+ AsyncTask::new(task)
+ }
+
+ /// Run the future on the specified nsIEventTarget.
+ pub fn spawn_onto(self, target: &nsIEventTarget) -> AsyncTask<F::Output> {
+ let config = Arc::new(TaskSpawnConfig {
+ name: self.name,
+ priority: self.priority,
+ options: self.options,
+ target: SpawnTarget::EventTarget(RefPtr::new(target)),
+ });
+ let (runnable, task) = async_task::spawn(self.future, move |runnable| {
+ schedule(config.clone(), runnable)
+ });
+ runnable.schedule();
+ AsyncTask::new(task)
+ }
+}
+
+impl<F> TaskBuilder<F>
+where
+ F: Future + 'static,
+ F::Output: 'static,
+{
+ /// Run the future on the current thread.
+ ///
+ /// Unlike the other `spawn` methods, this method supports non-Send futures.
+ ///
+ /// # Panics
+ ///
+ /// This method may panic if run on a thread which cannot run local futures
+ /// (e.g. due to it is not being an XPCOM thread, or if we are very late
+ /// during shutdown).
+ pub fn spawn_local(self) -> AsyncTask<F::Output> {
+ let current_thread = get_current_thread().expect("cannot get current thread");
+ let config = Arc::new(TaskSpawnConfig {
+ name: self.name,
+ priority: self.priority,
+ options: self.options,
+ target: SpawnTarget::EventTarget(RefPtr::new(current_thread.coerce())),
+ });
+ let (runnable, task) = async_task::spawn_local(self.future, move |runnable| {
+ schedule(config.clone(), runnable)
+ });
+ runnable.schedule();
+ AsyncTask::new(task)
+ }
+}
+
+/// Spawn a future onto the background task pool. The future will not be run on
+/// the main thread.
+pub fn spawn<F>(name: &'static str, future: F) -> AsyncTask<F::Output>
+where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+{
+ TaskBuilder::new(name, future).spawn()
+}
+
+/// Spawn a potentially-blocking future onto the background task pool. The
+/// future will not be run on the main thread.
+pub fn spawn_blocking<F>(name: &'static str, future: F) -> AsyncTask<F::Output>
+where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+{
+ TaskBuilder::new(name, future).may_block(true).spawn()
+}
+
+/// Spawn a local future onto the current thread.
+pub fn spawn_local<F>(name: &'static str, future: F) -> AsyncTask<F::Output>
+where
+ F: Future + 'static,
+ F::Output: 'static,
+{
+ TaskBuilder::new(name, future).spawn_local()
+}
+
+pub fn spawn_onto<F>(name: &'static str, target: &nsIEventTarget, future: F) -> AsyncTask<F::Output>
+where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+{
+ TaskBuilder::new(name, future).spawn_onto(target)
+}
+
+pub fn spawn_onto_blocking<F>(
+ name: &'static str,
+ target: &nsIEventTarget,
+ future: F,
+) -> AsyncTask<F::Output>
+where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+{
+ TaskBuilder::new(name, future)
+ .may_block(true)
+ .spawn_onto(target)
+}
diff --git a/xpcom/rust/moz_task/src/lib.rs b/xpcom/rust/moz_task/src/lib.rs
new file mode 100644
index 0000000000..2f0c0cfd0a
--- /dev/null
+++ b/xpcom/rust/moz_task/src/lib.rs
@@ -0,0 +1,378 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module wraps XPCOM threading functions with Rust functions
+//! to make it safer and more convenient to call the XPCOM functions.
+//! It also provides the Task trait and TaskRunnable struct,
+//! which make it easier to dispatch tasks to threads.
+
+mod dispatcher;
+pub use dispatcher::{dispatch_background_task, dispatch_local, dispatch_onto, RunnableBuilder};
+mod event_loop;
+mod executor;
+pub use executor::{
+ spawn, spawn_blocking, spawn_local, spawn_onto, spawn_onto_blocking, AsyncTask, TaskBuilder,
+};
+
+// Expose functions intended to be used only in gtest via this module.
+// We don't use a feature gate here to stop the need to compile all crates that
+// depend upon `moz_task` twice.
+pub mod gtest_only {
+ pub use crate::event_loop::spin_event_loop_until;
+}
+
+use nserror::nsresult;
+use nsstring::{nsACString, nsCString};
+use std::{ffi::CStr, marker::PhantomData, mem, ptr};
+use xpcom::{
+ getter_addrefs,
+ interfaces::{nsIEventTarget, nsIRunnable, nsISerialEventTarget, nsISupports, nsIThread},
+ AtomicRefcnt, RefCounted, RefPtr, XpCom,
+};
+
+extern "C" {
+ fn NS_GetCurrentThreadRust(result: *mut *const nsIThread) -> nsresult;
+ fn NS_GetMainThreadRust(result: *mut *const nsIThread) -> nsresult;
+ fn NS_IsMainThread() -> bool;
+ fn NS_NewNamedThreadWithDefaultStackSize(
+ name: *const nsACString,
+ result: *mut *const nsIThread,
+ event: *const nsIRunnable,
+ ) -> nsresult;
+ fn NS_IsOnCurrentThread(target: *const nsIEventTarget) -> bool;
+ fn NS_ProxyReleaseISupports(
+ name: *const libc::c_char,
+ target: *const nsIEventTarget,
+ doomed: *const nsISupports,
+ always_proxy: bool,
+ );
+ fn NS_CreateBackgroundTaskQueue(
+ name: *const libc::c_char,
+ target: *mut *const nsISerialEventTarget,
+ ) -> nsresult;
+ fn NS_DispatchBackgroundTask(event: *const nsIRunnable, flags: u32) -> nsresult;
+}
+
+pub fn get_current_thread() -> Result<RefPtr<nsIThread>, nsresult> {
+ getter_addrefs(|p| unsafe { NS_GetCurrentThreadRust(p) })
+}
+
+pub fn get_main_thread() -> Result<RefPtr<nsIThread>, nsresult> {
+ getter_addrefs(|p| unsafe { NS_GetMainThreadRust(p) })
+}
+
+pub fn is_main_thread() -> bool {
+ unsafe { NS_IsMainThread() }
+}
+
+// There's no OS requirement that thread names be static, but dynamic thread
+// names tend to conceal more than they reveal when processing large numbers of
+// crash reports.
+pub fn create_thread(name: &'static str) -> Result<RefPtr<nsIThread>, nsresult> {
+ getter_addrefs(|p| unsafe {
+ NS_NewNamedThreadWithDefaultStackSize(&*nsCString::from(name), p, ptr::null())
+ })
+}
+
+pub fn is_on_current_thread(target: &nsIEventTarget) -> bool {
+ unsafe { NS_IsOnCurrentThread(target) }
+}
+
+/// Creates a queue that runs tasks on the background thread pool. The tasks
+/// will run in the order they're dispatched, one after the other.
+pub fn create_background_task_queue(
+ name: &'static CStr,
+) -> Result<RefPtr<nsISerialEventTarget>, nsresult> {
+ getter_addrefs(|p| unsafe { NS_CreateBackgroundTaskQueue(name.as_ptr(), p) })
+}
+
+/// Dispatches a one-shot runnable to an event target, like a thread or a
+/// task queue, with the given options.
+///
+/// This function leaks the runnable if dispatch fails.
+///
+/// # Safety
+///
+/// As there is no guarantee that the runnable is actually `Send + Sync`, we
+/// can't know that it's safe to dispatch an `nsIRunnable` to any
+/// `nsIEventTarget`.
+pub unsafe fn dispatch_runnable(
+ runnable: &nsIRunnable,
+ target: &nsIEventTarget,
+ options: DispatchOptions,
+) -> Result<(), nsresult> {
+ // NOTE: DispatchFromScript performs an AddRef on `runnable` which is
+ // why this function leaks on failure.
+ target
+ .DispatchFromScript(runnable, options.flags())
+ .to_result()
+}
+
+/// Dispatches a one-shot task runnable to the background thread pool with the
+/// given options. The task may run concurrently with other background tasks.
+/// If you need tasks to run in a specific order, please create a background
+/// task queue using `create_background_task_queue`, and dispatch tasks to it
+/// instead.
+///
+/// This function leaks the runnable if dispatch fails. This avoids a race where
+/// a runnable can be destroyed on either the original or target thread, which
+/// is important if the runnable holds thread-unsafe members.
+///
+/// ### Safety
+///
+/// As there is no guarantee that the runnable is actually `Send + Sync`, we
+/// can't know that it's safe to dispatch an `nsIRunnable` to any
+/// `nsIEventTarget`.
+pub unsafe fn dispatch_background_task_runnable(
+ runnable: &nsIRunnable,
+ options: DispatchOptions,
+) -> Result<(), nsresult> {
+ // This eventually calls the non-`already_AddRefed<nsIRunnable>` overload of
+ // `nsIEventTarget::Dispatch` (see xpcom/threads/nsIEventTarget.idl#20-25),
+ // which adds an owning reference and leaks if dispatch fails.
+ NS_DispatchBackgroundTask(runnable, options.flags()).to_result()
+}
+
+/// Options to control how task runnables are dispatched.
+#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
+pub struct DispatchOptions(u32);
+
+impl Default for DispatchOptions {
+ #[inline]
+ fn default() -> Self {
+ DispatchOptions(nsIEventTarget::DISPATCH_NORMAL)
+ }
+}
+
+impl DispatchOptions {
+ /// Creates a blank set of options. The runnable will be dispatched using
+ /// the default mode.
+ #[inline]
+ pub fn new() -> Self {
+ DispatchOptions::default()
+ }
+
+ /// Indicates whether or not the dispatched runnable may block its target
+ /// thread by waiting on I/O. If `true`, the runnable may be dispatched to a
+ /// dedicated thread pool, leaving the main pool free for CPU-bound tasks.
+ #[inline]
+ pub fn may_block(self, may_block: bool) -> DispatchOptions {
+ const FLAG: u32 = nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK;
+ if may_block {
+ DispatchOptions(self.flags() | FLAG)
+ } else {
+ DispatchOptions(self.flags() & !FLAG)
+ }
+ }
+
+ /// Specifies that the dispatch is occurring from a running event that was
+ /// dispatched to the same event target, and that event is about to finish.
+ ///
+ /// A thread pool can use this as an optimization hint to not spin up
+ /// another thread, since the current thread is about to become idle.
+ ///
+ /// Setting this flag is unsafe, as it may only be used from the target
+ /// event target when the event is about to finish.
+ #[inline]
+ pub unsafe fn at_end(self, may_block: bool) -> DispatchOptions {
+ const FLAG: u32 = nsIEventTarget::DISPATCH_AT_END;
+ if may_block {
+ DispatchOptions(self.flags() | FLAG)
+ } else {
+ DispatchOptions(self.flags() & !FLAG)
+ }
+ }
+
+ /// Returns the set of bitflags to pass to `DispatchFromScript`.
+ #[inline]
+ fn flags(self) -> u32 {
+ self.0
+ }
+}
+
+/// A task represents an operation that asynchronously executes on a target
+/// thread, and returns its result to the original thread.
+///
+/// # Alternatives
+///
+/// This trait is no longer necessary for basic tasks to be dispatched to
+/// another thread with a callback on the originating thread. `moz_task` now has
+/// a series of more rust-like primitives which can be used instead. For
+/// example, it may be preferable to use the async executor over `Task`:
+///
+/// ```ignore
+/// // Spawn a task onto the background task pool, and capture the result of its
+/// // execution.
+/// let bg_task = moz_task::spawn("Example", async move {
+/// do_background_work(captured_state)
+/// });
+///
+/// // Spawn another task on the calling thread which will await on the result
+/// // of the async operation, and invoke a non-Send callback. This task won't
+/// // be awaited on, so needs to be `detach`-ed.
+/// moz_task::spawn_local("Example", async move {
+/// callback.completed(bg_task.await);
+/// })
+/// .detach();
+/// ```
+///
+/// If no result is needed, the task returned from `spawn` may be also detached
+/// directly.
+pub trait Task {
+ // FIXME: These could accept `&mut`.
+ fn run(&self);
+ fn done(&self) -> Result<(), nsresult>;
+}
+
+pub struct TaskRunnable {
+ name: &'static str,
+ task: Box<dyn Task + Send + Sync>,
+}
+
+impl TaskRunnable {
+ // XXX: Fixme: clean up this old API. (bug 1744312)
+ pub fn new(
+ name: &'static str,
+ task: Box<dyn Task + Send + Sync>,
+ ) -> Result<TaskRunnable, nsresult> {
+ Ok(TaskRunnable { name, task })
+ }
+
+ pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), nsresult> {
+ self.dispatch_with_options(target, DispatchOptions::default())
+ }
+
+ pub fn dispatch_with_options(
+ self,
+ target: &nsIEventTarget,
+ options: DispatchOptions,
+ ) -> Result<(), nsresult> {
+ // Perform `task.run()` on a background thread.
+ let task = self.task;
+ let handle = TaskBuilder::new(self.name, async move {
+ task.run();
+ task
+ })
+ .options(options)
+ .spawn_onto(target);
+
+ // Run `task.done()` on the starting thread once the background thread
+ // is done with the task.
+ spawn_local(self.name, async move {
+ let task = handle.await;
+ let _ = task.done();
+ })
+ .detach();
+ Ok(())
+ }
+
+ pub fn dispatch_background_task_with_options(
+ self,
+ options: DispatchOptions,
+ ) -> Result<(), nsresult> {
+ // Perform `task.run()` on a background thread.
+ let task = self.task;
+ let handle = TaskBuilder::new(self.name, async move {
+ task.run();
+ task
+ })
+ .options(options)
+ .spawn();
+
+ // Run `task.done()` on the starting thread once the background thread
+ // is done with the task.
+ spawn_local(self.name, async move {
+ let task = handle.await;
+ let _ = task.done();
+ })
+ .detach();
+ Ok(())
+ }
+}
+
+pub type ThreadPtrHandle<T> = RefPtr<ThreadPtrHolder<T>>;
+
+/// A Rust analog to `nsMainThreadPtrHolder` that wraps an `nsISupports` object
+/// with thread-safe refcounting. The holder keeps one reference to the wrapped
+/// object that's released when the holder's refcount reaches zero.
+pub struct ThreadPtrHolder<T: XpCom + 'static> {
+ ptr: *const T,
+ marker: PhantomData<T>,
+ name: &'static CStr,
+ owning_thread: RefPtr<nsIThread>,
+ refcnt: AtomicRefcnt,
+}
+
+unsafe impl<T: XpCom + 'static> Send for ThreadPtrHolder<T> {}
+unsafe impl<T: XpCom + 'static> Sync for ThreadPtrHolder<T> {}
+
+unsafe impl<T: XpCom + 'static> RefCounted for ThreadPtrHolder<T> {
+ unsafe fn addref(&self) {
+ self.refcnt.inc();
+ }
+
+ unsafe fn release(&self) {
+ let rc = self.refcnt.dec();
+ if rc == 0 {
+ // Once the holder's count reaches zero, release the wrapped
+ // object...
+ if !self.ptr.is_null() {
+ // The holder can be released on any thread. If we're on the
+ // owning thread, we can release the object directly. Otherwise,
+ // we need to post a proxy release event to release the object
+ // on the owning thread.
+ if is_on_current_thread(&self.owning_thread) {
+ (*self.ptr).release()
+ } else {
+ NS_ProxyReleaseISupports(
+ self.name.as_ptr(),
+ self.owning_thread.coerce(),
+ self.ptr as *const T as *const nsISupports,
+ false,
+ );
+ }
+ }
+ // ...And deallocate the holder.
+ mem::drop(Box::from_raw(self as *const Self as *mut Self));
+ }
+ }
+}
+
+impl<T: XpCom + 'static> ThreadPtrHolder<T> {
+ /// Creates a new owning thread pointer holder. Returns an error if the
+ /// thread manager has shut down. Panics if `name` isn't a valid C string.
+ pub fn new(name: &'static CStr, ptr: RefPtr<T>) -> Result<RefPtr<Self>, nsresult> {
+ let owning_thread = get_current_thread()?;
+ // Take ownership of the `RefPtr`. This does _not_ decrement its
+ // refcount, which is what we want. Once we've released all references
+ // to the holder, we'll release the wrapped `RefPtr`.
+ let raw: *const T = &*ptr;
+ mem::forget(ptr);
+ unsafe {
+ let boxed = Box::new(ThreadPtrHolder {
+ name,
+ ptr: raw,
+ marker: PhantomData,
+ owning_thread,
+ refcnt: AtomicRefcnt::new(),
+ });
+ Ok(RefPtr::from_raw(Box::into_raw(boxed)).unwrap())
+ }
+ }
+
+ /// Returns the wrapped object's owning thread.
+ pub fn owning_thread(&self) -> &nsIThread {
+ &self.owning_thread
+ }
+
+ /// Returns the wrapped object if called from the owning thread, or
+ /// `None` if called from any other thread.
+ pub fn get(&self) -> Option<&T> {
+ if is_on_current_thread(&self.owning_thread) && !self.ptr.is_null() {
+ unsafe { Some(&*self.ptr) }
+ } else {
+ None
+ }
+ }
+}
diff --git a/xpcom/rust/nserror/Cargo.toml b/xpcom/rust/nserror/Cargo.toml
new file mode 100644
index 0000000000..4fca5a4c2c
--- /dev/null
+++ b/xpcom/rust/nserror/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "nserror"
+version = "0.1.0"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+license = "MPL-2.0"
+description = "Rust bindings to xpcom nsresult and NS_ERROR_ values"
+edition = "2018"
+
+[dependencies]
+nsstring = { path = "../nsstring" }
+mozbuild = "0.1"
diff --git a/xpcom/rust/nserror/src/lib.rs b/xpcom/rust/nserror/src/lib.rs
new file mode 100644
index 0000000000..e4107d1b57
--- /dev/null
+++ b/xpcom/rust/nserror/src/lib.rs
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use nsstring::{nsACString, nsCString};
+use std::error::Error;
+use std::fmt;
+
+/// The type of errors in gecko. Uses a newtype to provide additional type
+/// safety in Rust and #[repr(transparent)] to ensure the same representation
+/// as the C++ equivalent.
+#[repr(transparent)]
+#[allow(non_camel_case_types)]
+#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct nsresult(pub u32);
+
+impl nsresult {
+ pub fn failed(self) -> bool {
+ (self.0 >> 31) != 0
+ }
+
+ pub fn succeeded(self) -> bool {
+ !self.failed()
+ }
+
+ pub fn to_result(self) -> Result<(), nsresult> {
+ if self.failed() {
+ Err(self)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Get a printable name for the nsresult error code. This function returns
+ /// a nsCString<'static>, which implements `Display`.
+ pub fn error_name(self) -> nsCString {
+ let mut cstr = nsCString::new();
+ unsafe {
+ Gecko_GetErrorName(self, &mut *cstr);
+ }
+ cstr
+ }
+}
+
+impl fmt::Display for nsresult {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.error_name())
+ }
+}
+
+impl fmt::Debug for nsresult {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.error_name())
+ }
+}
+
+impl<T, E> From<Result<T, E>> for nsresult
+where
+ E: Into<nsresult>,
+{
+ fn from(result: Result<T, E>) -> nsresult {
+ match result {
+ Ok(_) => NS_OK,
+ Err(e) => e.into(),
+ }
+ }
+}
+
+impl Error for nsresult {}
+
+extern "C" {
+ fn Gecko_GetErrorName(rv: nsresult, cstr: *mut nsACString);
+}
+
+mod error_list {
+ include!(mozbuild::objdir_path!("xpcom/base/error_list.rs"));
+}
+
+pub use error_list::*;
diff --git a/xpcom/rust/nsstring/Cargo.toml b/xpcom/rust/nsstring/Cargo.toml
new file mode 100644
index 0000000000..c2f8e34b45
--- /dev/null
+++ b/xpcom/rust/nsstring/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "nsstring"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+license = "MPL-2.0"
+description = "Rust bindings to xpcom string types"
+edition = "2018"
+
+[features]
+gecko_debug = []
+
+[dependencies]
+bitflags = "1.0"
+encoding_rs = "0.8.0"
diff --git a/xpcom/rust/nsstring/src/conversions.rs b/xpcom/rust/nsstring/src/conversions.rs
new file mode 100644
index 0000000000..c72c195c08
--- /dev/null
+++ b/xpcom/rust/nsstring/src/conversions.rs
@@ -0,0 +1,751 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ nsACString, nsAString, nsCStringLike, BulkWriteOk, Gecko_FallibleAssignCString,
+ Latin1StringLike,
+};
+use encoding_rs::mem::*;
+use encoding_rs::Encoding;
+use std::slice;
+
+/// Required math stated in the docs of
+/// `convert_utf16_to_utf8()`.
+#[inline(always)]
+fn times_three(a: usize) -> Option<usize> {
+ a.checked_mul(3)
+}
+
+#[inline(always)]
+fn identity(a: usize) -> Option<usize> {
+ Some(a)
+}
+
+#[inline(always)]
+fn plus_one(a: usize) -> Option<usize> {
+ a.checked_add(1)
+}
+
+/// Typical cache line size per
+/// https://stackoverflow.com/questions/14707803/line-size-of-l1-and-l2-caches
+///
+/// For consistent behavior, not trying to use 128 on aarch64
+/// or other fanciness like that.
+const CACHE_LINE: usize = 64;
+
+const CACHE_LINE_MASK: usize = CACHE_LINE - 1;
+
+/// Returns true if the string is both longer than a cache line
+/// and the first cache line is ASCII.
+#[inline(always)]
+fn long_string_starts_with_ascii(buffer: &[u8]) -> bool {
+ // We examine data only up to the end of the cache line
+ // to make this check minimally disruptive.
+ if buffer.len() <= CACHE_LINE {
+ return false;
+ }
+ let bound = CACHE_LINE - ((buffer.as_ptr() as usize) & CACHE_LINE_MASK);
+ is_ascii(&buffer[..bound])
+}
+
+/// Returns true if the string is both longer than two cache lines
+/// and the first two cache lines are Basic Latin.
+#[inline(always)]
+fn long_string_stars_with_basic_latin(buffer: &[u16]) -> bool {
+ // We look at two cache lines with code unit size of two. There is need
+ // to look at more than one cache line in the UTF-16 case, because looking
+ // at just one cache line wouldn't catch non-ASCII Latin with high enough
+ // probability with Latin-script languages that have relatively infrequent
+ // non-ASCII characters.
+ if buffer.len() <= CACHE_LINE {
+ return false;
+ }
+ let bound = (CACHE_LINE * 2 - ((buffer.as_ptr() as usize) & CACHE_LINE_MASK)) / 2;
+ is_basic_latin(&buffer[..bound])
+}
+
+// Ignoring the copy avoidance complications of conversions between Latin1 and
+// UTF-8, a conversion function has the outward form of
+// `fn F(&mut self, other: &[T], old_len: usize) -> Result<BulkWriteOk, ()>`,
+// where `T` is either `u8` or `u16`. `other` is the slice whose converted
+// content are to be appended to `self` and `old_len` indicates how many
+// code unit of `self` are to be preserved (0 for the assignment case and
+// `self.len()` for the appending case).
+//
+// As implementation parameters a conversion function needs to know the
+// math for computing the worst case conversion length in code units given
+// the input length in code units. For a _constant conversion_ the number
+// of code units the conversion produces equals the number of code units
+// in the input. For a _shinking conversion_ the maximum number of code
+// units the conversion can produce equals the number of code units in
+// the input, but the conversion can produce fewer code units. Still, due
+// to implementation details, the function might want _one_ unit more of
+// output space. For an _expanding conversion_ (no need for macro), the
+// minimum number of code units produced by the conversion is the number
+// of code units in the input, but the conversion can produce more.
+//
+// Copy avoidance conversions avoid copying a refcounted buffer when it's
+// ASCII-only.
+//
+// Internally, a conversion function needs to know the underlying
+// encoding_rs conversion function, the math for computing the required
+// output buffer size and, depending on the case, the underlying
+// encoding_rs ASCII prefix handling function.
+
+/// A conversion where the number of code units in the output is potentially
+/// smaller than the number of code units in the input.
+///
+/// Takes the name of the method to be generated, the name of the conversion
+/// function and the type of the input slice.
+///
+/// `$name` is the name of the function to generate
+/// `$convert` is the underlying `encoding_rs::mem` function to use
+/// `$other_ty` is the type of the input slice
+/// `$math` is the worst-case length math that `$convert` expects
+macro_rules! shrinking_conversion {
+ (name = $name:ident,
+ convert = $convert:ident,
+ other_ty = $other_ty:ty,
+ math = $math:ident) => {
+ fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<BulkWriteOk, ()> {
+ let needed = $math(other.len()).ok_or(())?;
+ let mut handle =
+ unsafe { self.bulk_write(old_len.checked_add(needed).ok_or(())?, old_len, false)? };
+ let written = $convert(other, &mut handle.as_mut_slice()[old_len..]);
+ let new_len = old_len + written;
+ Ok(handle.finish(new_len, new_len > CACHE_LINE))
+ }
+ };
+}
+
+/// A conversion where the number of code units in the output is always equal
+/// to the number of code units in the input.
+///
+/// Takes the name of the method to be generated, the name of the conversion
+/// function and the type of the input slice.
+///
+/// `$name` is the name of the function to generate
+/// `$convert` is the underlying `encoding_rs::mem` function to use
+/// `$other_ty` is the type of the input slice
+macro_rules! constant_conversion {
+ (name = $name:ident,
+ convert = $convert:ident,
+ other_ty = $other_ty:ty) => {
+ fn $name(
+ &mut self,
+ other: $other_ty,
+ old_len: usize,
+ allow_shrinking: bool,
+ ) -> Result<BulkWriteOk, ()> {
+ let new_len = old_len.checked_add(other.len()).ok_or(())?;
+ let mut handle = unsafe { self.bulk_write(new_len, old_len, allow_shrinking)? };
+ $convert(other, &mut handle.as_mut_slice()[old_len..]);
+ Ok(handle.finish(new_len, false))
+ }
+ };
+}
+
+/// An intermediate check for avoiding a copy and having an `nsStringBuffer`
+/// refcount increment instead when both `self` and `other` are `nsACString`s,
+/// `other` is entirely ASCII and all old data in `self` is discarded.
+///
+/// `$name` is the name of the function to generate
+/// `$impl` is the underlying conversion that takes a slice and that is used
+/// when we can't just adopt the incoming buffer as-is
+/// `$string_like` is the kind of input taken
+macro_rules! ascii_copy_avoidance {
+ (name = $name:ident,
+ implementation = $implementation:ident,
+ string_like = $string_like:ident) => {
+ fn $name<T: $string_like + ?Sized>(
+ &mut self,
+ other: &T,
+ old_len: usize,
+ ) -> Result<BulkWriteOk, ()> {
+ let adapter = other.adapt();
+ let other_slice = adapter.as_ref();
+ let num_ascii = if adapter.is_abstract() && old_len == 0 {
+ let up_to = Encoding::ascii_valid_up_to(other_slice);
+ if up_to == other_slice.len() {
+ // Calling something whose argument can be obtained from
+ // the adapter rather than an nsStringLike avoids a huge
+ // lifetime mess by keeping nsStringLike and
+ // Latin1StringLike free of lifetime interdependencies.
+ if unsafe { Gecko_FallibleAssignCString(self, other.adapt().as_ptr()) } {
+ return Ok(BulkWriteOk {});
+ } else {
+ return Err(());
+ }
+ }
+ Some(up_to)
+ } else {
+ None
+ };
+ self.$implementation(other_slice, old_len, num_ascii)
+ }
+ };
+}
+
+impl nsAString {
+ // Valid UTF-8 to UTF-16
+
+ // Documentation says the destination buffer needs to have
+ // as many code units as the input.
+ shrinking_conversion!(
+ name = fallible_append_str_impl,
+ convert = convert_str_to_utf16,
+ other_ty = &str,
+ math = identity
+ );
+
+ /// Convert a valid UTF-8 string into valid UTF-16 and replace the content
+ /// of this string with the conversion result.
+ pub fn assign_str(&mut self, other: &str) {
+ self.fallible_append_str_impl(other, 0)
+ .expect("Out of memory");
+ }
+
+ /// Convert a valid UTF-8 string into valid UTF-16 and fallibly replace the
+ /// content of this string with the conversion result.
+ pub fn fallible_assign_str(&mut self, other: &str) -> Result<(), ()> {
+ self.fallible_append_str_impl(other, 0).map(|_| ())
+ }
+
+ /// Convert a valid UTF-8 string into valid UTF-16 and append the conversion
+ /// to this string.
+ pub fn append_str(&mut self, other: &str) {
+ let len = self.len();
+ self.fallible_append_str_impl(other, len)
+ .expect("Out of memory");
+ }
+
+ /// Convert a valid UTF-8 string into valid UTF-16 and fallibly append the
+ /// conversion to this string.
+ pub fn fallible_append_str(&mut self, other: &str) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_str_impl(other, len).map(|_| ())
+ }
+
+ // Potentially-invalid UTF-8 to UTF-16
+
+ // Documentation says the destination buffer needs to have
+ // one more code unit than the input.
+ shrinking_conversion!(
+ name = fallible_append_utf8_impl,
+ convert = convert_utf8_to_utf16,
+ other_ty = &[u8],
+ math = plus_one
+ );
+
+ /// Convert a potentially-invalid UTF-8 string into valid UTF-16
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// replace the content of this string with the conversion result.
+ pub fn assign_utf8(&mut self, other: &[u8]) {
+ self.fallible_append_utf8_impl(other, 0)
+ .expect("Out of memory");
+ }
+
+ /// Convert a potentially-invalid UTF-8 string into valid UTF-16
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// fallibly replace the content of this string with the conversion result.
+ pub fn fallible_assign_utf8(&mut self, other: &[u8]) -> Result<(), ()> {
+ self.fallible_append_utf8_impl(other, 0).map(|_| ())
+ }
+
+ /// Convert a potentially-invalid UTF-8 string into valid UTF-16
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// append the conversion result to this string.
+ pub fn append_utf8(&mut self, other: &[u8]) {
+ let len = self.len();
+ self.fallible_append_utf8_impl(other, len)
+ .expect("Out of memory");
+ }
+
+ /// Convert a potentially-invalid UTF-8 string into valid UTF-16
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// fallibly append the conversion result to this string.
+ pub fn fallible_append_utf8(&mut self, other: &[u8]) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_utf8_impl(other, len).map(|_| ())
+ }
+
+ // Latin1 to UTF-16
+
+ constant_conversion!(
+ name = fallible_append_latin1_impl,
+ convert = convert_latin1_to_utf16,
+ other_ty = &[u8]
+ );
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-16 and replace the content of this string with the conversion result.
+ pub fn assign_latin1(&mut self, other: &[u8]) {
+ self.fallible_append_latin1_impl(other, 0, true)
+ .expect("Out of memory");
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-16 and fallibly replace the content of this string with the
+ /// conversion result.
+ pub fn fallible_assign_latin1(&mut self, other: &[u8]) -> Result<(), ()> {
+ self.fallible_append_latin1_impl(other, 0, true).map(|_| ())
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-16 and append the conversion result to this string.
+ pub fn append_latin1(&mut self, other: &[u8]) {
+ let len = self.len();
+ self.fallible_append_latin1_impl(other, len, false)
+ .expect("Out of memory");
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-16 and fallibly append the conversion result to this string.
+ pub fn fallible_append_latin1(&mut self, other: &[u8]) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_latin1_impl(other, len, false)
+ .map(|_| ())
+ }
+}
+
+impl nsACString {
+ // UTF-16 to UTF-8
+
+ fn fallible_append_utf16_to_utf8_impl(
+ &mut self,
+ other: &[u16],
+ old_len: usize,
+ ) -> Result<BulkWriteOk, ()> {
+ // We first size the buffer for ASCII if the first two cache lines are ASCII. If that turns out
+ // not to be enough, we size for the worst case given the length of the remaining input at that
+ // point. BUT if the worst case fits inside the inline capacity of an autostring, we skip
+ // the ASCII stuff.
+ let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() {
+ let worst_case = times_three(other.len()).ok_or(())?;
+ if worst_case <= inline_capacity {
+ Some(worst_case)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+ let (filled, read, mut handle) =
+ if worst_case_needed.is_none() && long_string_stars_with_basic_latin(other) {
+ let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
+ let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? };
+ let (read, written) =
+ convert_utf16_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]);
+ let left = other.len() - read;
+ if left == 0 {
+ return Ok(handle.finish(old_len + written, true));
+ }
+ let filled = old_len + written;
+ let needed = times_three(left).ok_or(())?;
+ let new_len = filled.checked_add(needed).ok_or(())?;
+ unsafe {
+ handle.restart_bulk_write(new_len, filled, false)?;
+ }
+ (filled, read, handle)
+ } else {
+ // Started with non-ASCII. Compute worst case
+ let needed = if let Some(n) = worst_case_needed {
+ n
+ } else {
+ times_three(other.len()).ok_or(())?
+ };
+ let new_len = old_len.checked_add(needed).ok_or(())?;
+ let handle = unsafe { self.bulk_write(new_len, old_len, false)? };
+ (old_len, 0, handle)
+ };
+ let written = convert_utf16_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]);
+ Ok(handle.finish(filled + written, true))
+ }
+
+ /// Convert a potentially-invalid UTF-16 string into valid UTF-8
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// replace the content of this string with the conversion result.
+ pub fn assign_utf16_to_utf8(&mut self, other: &[u16]) {
+ self.fallible_append_utf16_to_utf8_impl(other, 0)
+ .expect("Out of memory");
+ }
+
+ /// Convert a potentially-invalid UTF-16 string into valid UTF-8
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// fallibly replace the content of this string with the conversion result.
+ pub fn fallible_assign_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> {
+ self.fallible_append_utf16_to_utf8_impl(other, 0)
+ .map(|_| ())
+ }
+
+ /// Convert a potentially-invalid UTF-16 string into valid UTF-8
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// append the conversion result to this string.
+ pub fn append_utf16_to_utf8(&mut self, other: &[u16]) {
+ let len = self.len();
+ self.fallible_append_utf16_to_utf8_impl(other, len)
+ .expect("Out of memory");
+ }
+
+ /// Convert a potentially-invalid UTF-16 string into valid UTF-8
+ /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
+ /// fallibly append the conversion result to this string.
+ pub fn fallible_append_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_utf16_to_utf8_impl(other, len)
+ .map(|_| ())
+ }
+
+ // UTF-16 to Latin1
+
+ constant_conversion!(
+ name = fallible_append_utf16_to_latin1_lossy_impl,
+ convert = convert_utf16_to_latin1_lossy,
+ other_ty = &[u16]
+ );
+
+ /// Convert a UTF-16 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// replace the content of this string with the conversion result.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-16,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) {
+ self.fallible_append_utf16_to_latin1_lossy_impl(other, 0, true)
+ .expect("Out of memory");
+ }
+
+ /// Convert a UTF-16 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// fallibly replace the content of this string with the conversion result.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-16,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn fallible_assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> {
+ self.fallible_append_utf16_to_latin1_lossy_impl(other, 0, true)
+ .map(|_| ())
+ }
+
+ /// Convert a UTF-16 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// append the conversion result to this string.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-16,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn append_utf16_to_latin1_lossy(&mut self, other: &[u16]) {
+ let len = self.len();
+ self.fallible_append_utf16_to_latin1_lossy_impl(other, len, false)
+ .expect("Out of memory");
+ }
+
+ /// Convert a UTF-16 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// fallibly append the conversion result to this string.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-16,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn fallible_append_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_utf16_to_latin1_lossy_impl(other, len, false)
+ .map(|_| ())
+ }
+
+ // UTF-8 to Latin1
+
+ ascii_copy_avoidance!(
+ name = fallible_append_utf8_to_latin1_lossy_check,
+ implementation = fallible_append_utf8_to_latin1_lossy_impl,
+ string_like = nsCStringLike
+ );
+
+ fn fallible_append_utf8_to_latin1_lossy_impl(
+ &mut self,
+ other: &[u8],
+ old_len: usize,
+ maybe_num_ascii: Option<usize>,
+ ) -> Result<BulkWriteOk, ()> {
+ let new_len = old_len.checked_add(other.len()).ok_or(())?;
+ let num_ascii = maybe_num_ascii.unwrap_or(0);
+ // Already checked for overflow above, so this can't overflow.
+ let old_len_plus_num_ascii = old_len + num_ascii;
+ let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? };
+ let written = {
+ let buffer = handle.as_mut_slice();
+ if num_ascii != 0 {
+ (&mut buffer[old_len..old_len_plus_num_ascii]).copy_from_slice(&other[..num_ascii]);
+ }
+ convert_utf8_to_latin1_lossy(&other[num_ascii..], &mut buffer[old_len_plus_num_ascii..])
+ };
+ Ok(handle.finish(old_len_plus_num_ascii + written, true))
+ }
+
+ /// Convert a UTF-8 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// replace the content of this string with the conversion result.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-8,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn assign_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(&mut self, other: &T) {
+ self.fallible_append_utf8_to_latin1_lossy_check(other, 0)
+ .expect("Out of memory");
+ }
+
+ /// Convert a UTF-8 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// fallibly replace the content of this string with the conversion result.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-8,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn fallible_assign_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(
+ &mut self,
+ other: &T,
+ ) -> Result<(), ()> {
+ self.fallible_append_utf8_to_latin1_lossy_check(other, 0)
+ .map(|_| ())
+ }
+
+ /// Convert a UTF-8 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// append the conversion result to this string.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-8,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn append_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(&mut self, other: &T) {
+ let len = self.len();
+ self.fallible_append_utf8_to_latin1_lossy_check(other, len)
+ .expect("Out of memory");
+ }
+
+ /// Convert a UTF-8 string whose all code points are below U+0100 into
+ /// a Latin1 (scalar value is byte value; not windows-1252!) string and
+ /// fallibly append the conversion result to this string.
+ ///
+ /// # Panics
+ ///
+ /// If the input contains code points above U+00FF or is not valid UTF-8,
+ /// panics in debug mode and produces garbage in a memory-safe way in
+ /// release builds. The nature of the garbage may differ based on CPU
+ /// architecture and must not be relied upon.
+ pub fn fallible_append_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(
+ &mut self,
+ other: &T,
+ ) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_utf8_to_latin1_lossy_check(other, len)
+ .map(|_| ())
+ }
+
+ // Latin1 to UTF-8 CString
+
+ ascii_copy_avoidance!(
+ name = fallible_append_latin1_to_utf8_check,
+ implementation = fallible_append_latin1_to_utf8_impl,
+ string_like = Latin1StringLike
+ );
+
+ fn fallible_append_latin1_to_utf8_impl(
+ &mut self,
+ other: &[u8],
+ old_len: usize,
+ maybe_num_ascii: Option<usize>,
+ ) -> Result<BulkWriteOk, ()> {
+ let (filled, read, mut handle) = if let Some(num_ascii) = maybe_num_ascii {
+ // Wrapper checked for ASCII
+ let left = other.len() - num_ascii;
+ let filled = old_len + num_ascii;
+ let needed = left.checked_mul(2).ok_or(())?;
+ let new_len = filled.checked_add(needed).ok_or(())?;
+ let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? };
+ if num_ascii != 0 {
+ (&mut handle.as_mut_slice()[old_len..filled]).copy_from_slice(&other[..num_ascii]);
+ }
+ (filled, num_ascii, handle)
+ } else {
+ let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() {
+ let worst_case = other.len().checked_mul(2).ok_or(())?;
+ if worst_case <= inline_capacity {
+ Some(worst_case)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+ if worst_case_needed.is_none() && long_string_starts_with_ascii(other) {
+ // Wrapper didn't check for ASCII, so let's see if `other` starts with ASCII
+ // `other` starts with ASCII, so let's first size the buffer
+ // with optimism that it's ASCII-only.
+ let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
+ let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? };
+ let (read, written) =
+ convert_latin1_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]);
+ let left = other.len() - read;
+ let filled = old_len + written;
+ if left == 0 {
+ // `other` fit in the initial allocation
+ return Ok(handle.finish(filled, true));
+ }
+ let needed = left.checked_mul(2).ok_or(())?;
+ let new_len = filled.checked_add(needed).ok_or(())?;
+ unsafe {
+ handle.restart_bulk_write(new_len, filled, false)?;
+ }
+ (filled, read, handle)
+ } else {
+ // Started with non-ASCII. Assume worst case.
+ let needed = if let Some(n) = worst_case_needed {
+ n
+ } else {
+ other.len().checked_mul(2).ok_or(())?
+ };
+ let new_len = old_len.checked_add(needed).ok_or(())?;
+ let handle = unsafe { self.bulk_write(new_len, old_len, false)? };
+ (old_len, 0, handle)
+ }
+ };
+ let written = convert_latin1_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]);
+ Ok(handle.finish(filled + written, true))
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-8 and replace the content of this string with the conversion result.
+ pub fn assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) {
+ self.fallible_append_latin1_to_utf8_check(other, 0)
+ .expect("Out of memory");
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-8 and fallibly replace the content of this string with the
+ /// conversion result.
+ pub fn fallible_assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(
+ &mut self,
+ other: &T,
+ ) -> Result<(), ()> {
+ self.fallible_append_latin1_to_utf8_check(other, 0)
+ .map(|_| ())
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-8 and append the conversion result to this string.
+ pub fn append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) {
+ let len = self.len();
+ self.fallible_append_latin1_to_utf8_check(other, len)
+ .expect("Out of memory");
+ }
+
+ /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
+ /// into UTF-8 and fallibly append the conversion result to this string.
+ pub fn fallible_append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(
+ &mut self,
+ other: &T,
+ ) -> Result<(), ()> {
+ let len = self.len();
+ self.fallible_append_latin1_to_utf8_check(other, len)
+ .map(|_| ())
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn nsstring_fallible_append_utf8_impl(
+ this: *mut nsAString,
+ other: *const u8,
+ other_len: usize,
+ old_len: usize,
+) -> bool {
+ let other_slice = slice::from_raw_parts(other, other_len);
+ (*this)
+ .fallible_append_utf8_impl(other_slice, old_len)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn nsstring_fallible_append_latin1_impl(
+ this: *mut nsAString,
+ other: *const u8,
+ other_len: usize,
+ old_len: usize,
+ allow_shrinking: bool,
+) -> bool {
+ let other_slice = slice::from_raw_parts(other, other_len);
+ (*this)
+ .fallible_append_latin1_impl(other_slice, old_len, allow_shrinking)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn nscstring_fallible_append_utf16_to_utf8_impl(
+ this: *mut nsACString,
+ other: *const u16,
+ other_len: usize,
+ old_len: usize,
+) -> bool {
+ let other_slice = slice::from_raw_parts(other, other_len);
+ (*this)
+ .fallible_append_utf16_to_utf8_impl(other_slice, old_len)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn nscstring_fallible_append_utf16_to_latin1_lossy_impl(
+ this: *mut nsACString,
+ other: *const u16,
+ other_len: usize,
+ old_len: usize,
+ allow_shrinking: bool,
+) -> bool {
+ let other_slice = slice::from_raw_parts(other, other_len);
+ (*this)
+ .fallible_append_utf16_to_latin1_lossy_impl(other_slice, old_len, allow_shrinking)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn nscstring_fallible_append_utf8_to_latin1_lossy_check(
+ this: *mut nsACString,
+ other: *const nsACString,
+ old_len: usize,
+) -> bool {
+ (*this)
+ .fallible_append_utf8_to_latin1_lossy_check(&*other, old_len)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn nscstring_fallible_append_latin1_to_utf8_check(
+ this: *mut nsACString,
+ other: *const nsACString,
+ old_len: usize,
+) -> bool {
+ (*this)
+ .fallible_append_latin1_to_utf8_check(&*other, old_len)
+ .is_ok()
+}
diff --git a/xpcom/rust/nsstring/src/lib.rs b/xpcom/rust/nsstring/src/lib.rs
new file mode 100644
index 0000000000..521c2c8c04
--- /dev/null
+++ b/xpcom/rust/nsstring/src/lib.rs
@@ -0,0 +1,1543 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This module provides rust bindings for the XPCOM string types.
+//!
+//! # TL;DR (what types should I use)
+//!
+//! Use `&{mut,} nsA[C]String` for functions in rust which wish to take or
+//! mutate XPCOM strings. The other string types `Deref` to this type.
+//!
+//! Use `ns[C]String` (`ns[C]String` in C++) for string struct members, and as
+//! an intermediate between rust string data structures (such as `String` or
+//! `Vec<u16>`) and `&{mut,} nsA[C]String` (using `ns[C]String::from(value)`).
+//! These conversions will attempt to re-use the passed-in buffer, appending a
+//! null.
+//!
+//! Use `ns[C]Str` (`nsDependent[C]String` in C++) as an intermediate between
+//! borrowed rust data structures (such as `&str` and `&[u16]`) and `&{mut,}
+//! nsA[C]String` (using `ns[C]Str::from(value)`). These conversions should not
+//! perform any allocations. This type is not safe to share with `C++` as a
+//! struct field, but passing the borrowed `&{mut,} nsA[C]String` over FFI is
+//! safe.
+//!
+//! Use `*{const,mut} nsA[C]String` (`{const,} nsA[C]String*` in C++) for
+//! function arguments passed across the rust/C++ language boundary.
+//!
+//! There is currently no Rust equivalent to `nsAuto[C]String`. Implementing a
+//! type that contains a pointer to an inline buffer is difficult in Rust due
+//! to its move semantics, which require that it be safe to move a value by
+//! copying its bits. If such a type is genuinely needed at some point,
+//! <https://bugzilla.mozilla.org/show_bug.cgi?id=1403506#c6> has a sketch of
+//! how to emulate it via macros.
+//!
+//! # String Types
+//!
+//! ## `nsA[C]String`
+//!
+//! The core types in this module are `nsAString` and `nsACString`. These types
+//! are zero-sized as far as rust is concerned, and are safe to pass around
+//! behind both references (in rust code), and pointers (in C++ code). They
+//! represent a handle to a XPCOM string which holds either `u16` or `u8`
+//! characters respectively. The backing character buffer is guaranteed to live
+//! as long as the reference to the `nsAString` or `nsACString`.
+//!
+//! These types in rust are simply used as dummy types. References to them
+//! represent a pointer to the beginning of a variable-sized `#[repr(C)]` struct
+//! which is common between both C++ and Rust implementations. In C++, their
+//! corresponding types are also named `nsAString` or `nsACString`, and they are
+//! defined within the `nsTSubstring.{cpp,h}` file.
+//!
+//! ### Valid Operations
+//!
+//! An `&nsA[C]String` acts like rust's `&str`, in that it is a borrowed
+//! reference to the backing data. When used as an argument to other functions
+//! on `&mut nsA[C]String`, optimizations can be performed to avoid copying
+//! buffers, as information about the backing storage is preserved.
+//!
+//! An `&mut nsA[C]String` acts like rust's `&mut Cow<str>`, in that it is a
+//! mutable reference to a potentially borrowed string, which when modified will
+//! ensure that it owns its own backing storage. This type can be appended to
+//! with the methods `.append`, `.append_utf{8,16}`, and with the `write!`
+//! macro, and can be assigned to with `.assign`.
+//!
+//! ## `ns[C]Str<'a>`
+//!
+//! This type is an maybe-owned string type. It acts similarially to a
+//! `Cow<[{u8,u16}]>`. This type provides `Deref` and `DerefMut` implementations
+//! to `nsA[C]String`, which provides the methods for manipulating this type.
+//! This type's lifetime parameter, `'a`, represents the lifetime of the backing
+//! storage. When modified this type may re-allocate in order to ensure that it
+//! does not mutate its backing storage.
+//!
+//! `ns[C]Str`s can be constructed either with `ns[C]Str::new()`, which creates
+//! an empty `ns[C]Str<'static>`, or through one of the provided `From`
+//! implementations. Only `nsCStr` can be constructed `From<'a str>`, as
+//! constructing a `nsStr` would require transcoding. Use `ns[C]String` instead.
+//!
+//! When passing this type by reference, prefer passing a `&nsA[C]String` or
+//! `&mut nsA[C]String`. to passing this type.
+//!
+//! When passing this type across the language boundary, pass it as `*const
+//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a
+//! mutable reference.
+//!
+//! ## `ns[C]String`
+//!
+//! This type is an owned, null-terminated string type. This type provides
+//! `Deref` and `DerefMut` implementations to `nsA[C]String`, which provides the
+//! methods for manipulating this type.
+//!
+//! `ns[C]String`s can be constructed either with `ns[C]String::new()`, which
+//! creates an empty `ns[C]String`, or through one of the provided `From`
+//! implementations, which will try to avoid reallocating when possible,
+//! although a terminating `null` will be added.
+//!
+//! When passing this type by reference, prefer passing a `&nsA[C]String` or
+//! `&mut nsA[C]String`. to passing this type.
+//!
+//! When passing this type across the language boundary, pass it as `*const
+//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a
+//! mutable reference. This struct may also be included in `#[repr(C)]` structs
+//! shared with C++.
+//!
+//! ## `ns[C]StringRepr`
+//!
+//! This crate also provides the type `ns[C]StringRepr` which acts conceptually
+//! similar to an `ns[C]String`, however, it does not have a `Drop`
+//! implementation.
+//!
+//! If this type is dropped in rust, it will not free its backing storage. This
+//! can be useful when implementing FFI types which contain `ns[C]String` members
+//! which invoke their member's destructors through C++ code.
+
+#![allow(non_camel_case_types)]
+#![allow(clippy::missing_safety_doc)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::result_unit_err)]
+
+use bitflags::bitflags;
+use std::borrow;
+use std::cmp;
+use std::fmt;
+use std::marker::PhantomData;
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use std::os::raw::c_void;
+use std::ptr;
+use std::slice;
+use std::str;
+
+mod conversions;
+
+pub use self::conversions::nscstring_fallible_append_latin1_to_utf8_check;
+pub use self::conversions::nscstring_fallible_append_utf16_to_latin1_lossy_impl;
+pub use self::conversions::nscstring_fallible_append_utf16_to_utf8_impl;
+pub use self::conversions::nscstring_fallible_append_utf8_to_latin1_lossy_check;
+pub use self::conversions::nsstring_fallible_append_latin1_impl;
+pub use self::conversions::nsstring_fallible_append_utf8_impl;
+
+/// A type for showing that `finish()` was called on a `BulkWriteHandle`.
+/// Instantiating this type from elsewhere is basically an assertion that
+/// there is no `BulkWriteHandle` around, so be very careful with instantiating
+/// this type!
+pub struct BulkWriteOk;
+
+/// Semi-arbitrary threshold below which we don't care about shrinking
+/// buffers to size. Currently matches `CACHE_LINE` in the `conversions`
+/// module.
+const SHRINKING_THRESHOLD: usize = 64;
+
+///////////////////////////////////
+// Internal Implementation Flags //
+///////////////////////////////////
+
+bitflags! {
+ // While this has the same layout as u16, it cannot be passed
+ // over FFI safely as a u16.
+ #[repr(C)]
+ struct DataFlags: u16 {
+ const TERMINATED = 1 << 0; // IsTerminated returns true
+ const VOIDED = 1 << 1; // IsVoid returns true
+ const REFCOUNTED = 1 << 2; // mData points to a heap-allocated, shareable, refcounted
+ // buffer
+ const OWNED = 1 << 3; // mData points to a heap-allocated, raw buffer
+ const INLINE = 1 << 4; // mData points to a writable, inline buffer
+ const LITERAL = 1 << 5; // mData points to a string literal; TERMINATED will also be set
+ }
+}
+
+bitflags! {
+ // While this has the same layout as u16, it cannot be passed
+ // over FFI safely as a u16.
+ #[repr(C)]
+ struct ClassFlags: u16 {
+ const INLINE = 1 << 0; // |this|'s buffer is inline
+ const NULL_TERMINATED = 1 << 1; // |this| requires its buffer is null-terminated
+ }
+}
+
+////////////////////////////////////
+// Generic String Bindings Macros //
+////////////////////////////////////
+
+macro_rules! string_like {
+ {
+ char_t = $char_t: ty;
+
+ AString = $AString: ident;
+ String = $String: ident;
+ Str = $Str: ident;
+
+ StringLike = $StringLike: ident;
+ StringAdapter = $StringAdapter: ident;
+ } => {
+ /// This trait is implemented on types which are `ns[C]String`-like, in
+ /// that they can at very low cost be converted to a borrowed
+ /// `&nsA[C]String`. Unfortunately, the intermediate type
+ /// `ns[C]StringAdapter` is required as well due to types like `&[u8]`
+ /// needing to be (cheaply) wrapped in a `nsCString` on the stack to
+ /// create the `&nsACString`.
+ ///
+ /// This trait is used to DWIM when calling the methods on
+ /// `nsA[C]String`.
+ pub trait $StringLike {
+ fn adapt(&self) -> $StringAdapter;
+ }
+
+ impl<'a, T: $StringLike + ?Sized> $StringLike for &'a T {
+ fn adapt(&self) -> $StringAdapter {
+ <T as $StringLike>::adapt(*self)
+ }
+ }
+
+ impl<'a, T> $StringLike for borrow::Cow<'a, T>
+ where T: $StringLike + borrow::ToOwned + ?Sized {
+ fn adapt(&self) -> $StringAdapter {
+ <T as $StringLike>::adapt(self.as_ref())
+ }
+ }
+
+ impl $StringLike for $AString {
+ fn adapt(&self) -> $StringAdapter {
+ $StringAdapter::Abstract(self)
+ }
+ }
+
+ impl<'a> $StringLike for $Str<'a> {
+ fn adapt(&self) -> $StringAdapter {
+ $StringAdapter::Abstract(self)
+ }
+ }
+
+ impl $StringLike for $String {
+ fn adapt(&self) -> $StringAdapter {
+ $StringAdapter::Abstract(self)
+ }
+ }
+
+ impl $StringLike for [$char_t] {
+ fn adapt(&self) -> $StringAdapter {
+ $StringAdapter::Borrowed($Str::from(self))
+ }
+ }
+
+ impl $StringLike for Vec<$char_t> {
+ fn adapt(&self) -> $StringAdapter {
+ $StringAdapter::Borrowed($Str::from(&self[..]))
+ }
+ }
+
+ impl $StringLike for Box<[$char_t]> {
+ fn adapt(&self) -> $StringAdapter {
+ $StringAdapter::Borrowed($Str::from(&self[..]))
+ }
+ }
+ }
+}
+
+impl<'a> Drop for nsAStringBulkWriteHandle<'a> {
+ /// This only runs in error cases. In success cases, `finish()`
+ /// calls `forget(self)`.
+ fn drop(&mut self) {
+ if self.capacity == 0 {
+ // If capacity is 0, the string is a zero-length
+ // string, so we have nothing to do.
+ return;
+ }
+ // The old zero terminator may be gone by now, so we need
+ // to write a new one somewhere and make length match.
+ // We can use a length between 1 and self.capacity.
+ // Seems prudent to overwrite the uninitialized memory.
+ // Using the length 1 leaves the shortest memory to overwrite.
+ // U+FFFD is the safest placeholder. Merely truncating the
+ // string to a zero-length string might be dangerous in some
+ // scenarios. See
+ // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences
+ // for closely related scenario.
+ unsafe {
+ let mut this = self.string.as_repr_mut();
+ this.as_mut().length = 1u32;
+ *(this.as_mut().data.as_mut()) = 0xFFFDu16;
+ *(this.as_mut().data.as_ptr().add(1)) = 0;
+ }
+ }
+}
+
+impl<'a> Drop for nsACStringBulkWriteHandle<'a> {
+ /// This only runs in error cases. In success cases, `finish()`
+ /// calls `forget(self)`.
+ fn drop(&mut self) {
+ if self.capacity == 0 {
+ // If capacity is 0, the string is a zero-length
+ // string, so we have nothing to do.
+ return;
+ }
+ // The old zero terminator may be gone by now, so we need
+ // to write a new one somewhere and make length match.
+ // We can use a length between 1 and self.capacity.
+ // Seems prudent to overwrite the uninitialized memory.
+ // Using the length 1 leaves the shortest memory to overwrite.
+ // U+FFFD is the safest placeholder, but when it doesn't fit,
+ // let's use ASCII substitute. Merely truncating the
+ // string to a zero-length string might be dangerous in some
+ // scenarios. See
+ // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences
+ // for closely related scenario.
+ unsafe {
+ let mut this = self.string.as_repr_mut();
+ if self.capacity >= 3 {
+ this.as_mut().length = 3u32;
+ *(this.as_mut().data.as_mut()) = 0xEFu8;
+ *(this.as_mut().data.as_ptr().add(1)) = 0xBFu8;
+ *(this.as_mut().data.as_ptr().add(2)) = 0xBDu8;
+ *(this.as_mut().data.as_ptr().add(3)) = 0;
+ } else {
+ this.as_mut().length = 1u32;
+ *(this.as_mut().data.as_mut()) = 0x1Au8; // U+FFFD doesn't fit
+ *(this.as_mut().data.as_ptr().add(1)) = 0;
+ }
+ }
+ }
+}
+
+macro_rules! define_string_types {
+ {
+ char_t = $char_t: ty;
+
+ AString = $AString: ident;
+ String = $String: ident;
+ Str = $Str: ident;
+
+ StringLike = $StringLike: ident;
+ StringAdapter = $StringAdapter: ident;
+
+ StringRepr = $StringRepr: ident;
+ AutoStringRepr = $AutoStringRepr: ident;
+
+ BulkWriteHandle = $BulkWriteHandle: ident;
+
+ drop = $drop: ident;
+ assign = $assign: ident, $fallible_assign: ident;
+ take_from = $take_from: ident, $fallible_take_from: ident;
+ append = $append: ident, $fallible_append: ident;
+ set_length = $set_length: ident, $fallible_set_length: ident;
+ begin_writing = $begin_writing: ident, $fallible_begin_writing: ident;
+ start_bulk_write = $start_bulk_write: ident;
+ } => {
+ /// The representation of a `ns[C]String` type in C++. This type is
+ /// used internally by our definition of `ns[C]String` to ensure layout
+ /// compatibility with the C++ `ns[C]String` type.
+ ///
+ /// This type may also be used in place of a C++ `ns[C]String` inside of
+ /// struct definitions which are shared with C++, as it has identical
+ /// layout to our `ns[C]String` type.
+ ///
+ /// This struct will leak its data if dropped from rust. See the module
+ /// documentation for more information on this type.
+ #[repr(C)]
+ #[derive(Debug)]
+ pub struct $StringRepr {
+ data: ptr::NonNull<$char_t>,
+ length: u32,
+ dataflags: DataFlags,
+ classflags: ClassFlags,
+ }
+
+ impl $StringRepr {
+ fn new(classflags: ClassFlags) -> $StringRepr {
+ static NUL: $char_t = 0;
+ $StringRepr {
+ data: unsafe { ptr::NonNull::new_unchecked(&NUL as *const _ as *mut _) },
+ length: 0,
+ dataflags: DataFlags::TERMINATED | DataFlags::LITERAL,
+ classflags,
+ }
+ }
+ }
+
+ impl Deref for $StringRepr {
+ type Target = $AString;
+ fn deref(&self) -> &$AString {
+ unsafe {
+ &*(self as *const _ as *const $AString)
+ }
+ }
+ }
+
+ impl DerefMut for $StringRepr {
+ fn deref_mut(&mut self) -> &mut $AString {
+ unsafe {
+ &mut *(self as *mut _ as *mut $AString)
+ }
+ }
+ }
+
+ #[repr(C)]
+ #[derive(Debug)]
+ pub struct $AutoStringRepr {
+ super_repr: $StringRepr,
+ inline_capacity: u32,
+ }
+
+ pub struct $BulkWriteHandle<'a> {
+ string: &'a mut $AString,
+ capacity: usize,
+ }
+
+ impl<'a> $BulkWriteHandle<'a> {
+ fn new(string: &'a mut $AString, capacity: usize) -> Self {
+ $BulkWriteHandle{ string, capacity }
+ }
+
+ pub unsafe fn restart_bulk_write(&mut self,
+ capacity: usize,
+ units_to_preserve: usize,
+ allow_shrinking: bool) -> Result<(), ()> {
+ self.capacity =
+ self.string.start_bulk_write_impl(capacity,
+ units_to_preserve,
+ allow_shrinking)?;
+ Ok(())
+ }
+
+ pub fn finish(mut self, length: usize, allow_shrinking: bool) -> BulkWriteOk {
+ // NOTE: Drop is implemented outside the macro earlier in this file,
+ // because it needs to deal with different code unit representations
+ // for the REPLACEMENT CHARACTER in the UTF-16 and UTF-8 cases and
+ // needs to deal with a REPLACEMENT CHARACTER not fitting in the
+ // buffer in the UTF-8 case.
+ assert!(length <= self.capacity);
+ if length == 0 {
+ // `truncate()` is OK even when the string
+ // is in invalid state.
+ self.string.truncate();
+ mem::forget(self); // Don't run the failure path in drop()
+ return BulkWriteOk{};
+ }
+ if allow_shrinking && length > SHRINKING_THRESHOLD {
+ unsafe {
+ let _ = self.restart_bulk_write(length, length, true);
+ }
+ }
+ unsafe {
+ let mut this = self.string.as_repr_mut();
+ this.as_mut().length = length as u32;
+ *(this.as_mut().data.as_ptr().add(length)) = 0;
+ if cfg!(debug_assertions) {
+ // Overwrite the unused part in debug builds. Note
+ // that capacity doesn't include space for the zero
+ // terminator, so starting after the zero-terminator
+ // we wrote ends up overwriting the terminator space
+ // not reflected in the capacity number.
+ // write_bytes() takes care of multiplying the length
+ // by the size of T.
+ ptr::write_bytes(this.as_mut().data.as_ptr().add(length + 1),
+ 0xE4u8,
+ self.capacity - length);
+ }
+ // We don't have a Rust interface for mozilla/MemoryChecking.h,
+ // so let's just not communicate with MSan/Valgrind here.
+ }
+ mem::forget(self); // Don't run the failure path in drop()
+ BulkWriteOk{}
+ }
+
+ pub fn as_mut_slice(&mut self) -> &mut [$char_t] {
+ unsafe {
+ let mut this = self.string.as_repr_mut();
+ slice::from_raw_parts_mut(this.as_mut().data.as_ptr(), self.capacity)
+ }
+ }
+ }
+
+ /// This type is the abstract type which is used for interacting with
+ /// strings in rust. Each string type can derefence to an instance of
+ /// this type, which provides the useful operations on strings.
+ ///
+ /// NOTE: Rust thinks this type has a size of 0, because the data
+ /// associated with it is not necessarially safe to move. It is not safe
+ /// to construct a nsAString yourself, unless it is received by
+ /// dereferencing one of these types.
+ ///
+ /// NOTE: The `[u8; 0]` member is zero sized, and only exists to prevent
+ /// the construction by code outside of this module. It is used instead
+ /// of a private `()` member because the `improper_ctypes` lint complains
+ /// about some ZST members in `extern "C"` function declarations.
+ #[repr(C)]
+ pub struct $AString {
+ _prohibit_constructor: [u8; 0],
+ }
+
+ impl $AString {
+ /// Assign the value of `other` into self, overwriting any value
+ /// currently stored. Performs an optimized assignment when possible
+ /// if `other` is a `nsA[C]String`.
+ pub fn assign<T: $StringLike + ?Sized>(&mut self, other: &T) {
+ unsafe { $assign(self, other.adapt().as_ptr()) };
+ }
+
+ /// Assign the value of `other` into self, overwriting any value
+ /// currently stored. Performs an optimized assignment when possible
+ /// if `other` is a `nsA[C]String`.
+ ///
+ /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+ pub fn fallible_assign<T: $StringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+ if unsafe { $fallible_assign(self, other.adapt().as_ptr()) } {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ /// Take the value of `other` and set `self`, overwriting any value
+ /// currently stored. The passed-in string will be truncated.
+ pub fn take_from(&mut self, other: &mut $AString) {
+ unsafe { $take_from(self, other) };
+ }
+
+ /// Take the value of `other` and set `self`, overwriting any value
+ /// currently stored. If this function fails, the source string will
+ /// be left untouched, otherwise it will be truncated.
+ ///
+ /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+ pub fn fallible_take_from(&mut self, other: &mut $AString) -> Result<(), ()> {
+ if unsafe { $fallible_take_from(self, other) } {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ /// Append the value of `other` into self.
+ pub fn append<T: $StringLike + ?Sized>(&mut self, other: &T) {
+ unsafe { $append(self, other.adapt().as_ptr()) };
+ }
+
+ /// Append the value of `other` into self.
+ ///
+ /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+ pub fn fallible_append<T: $StringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+ if unsafe { $fallible_append(self, other.adapt().as_ptr()) } {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ /// Mark the string's data as void. If `true`, the string will be truncated.
+ ///
+ /// A void string is generally converted to a `null` JS value by bindings code.
+ pub fn set_is_void(&mut self, is_void: bool) {
+ if is_void {
+ self.truncate();
+ }
+ unsafe {
+ self.as_repr_mut().as_mut().dataflags.set(DataFlags::VOIDED, is_void);
+ }
+ }
+
+ /// Returns whether the string's data is voided.
+ pub fn is_void(&self) -> bool {
+ self.as_repr().dataflags.contains(DataFlags::VOIDED)
+ }
+
+ /// Set the length of the string to the passed-in length, and expand
+ /// the backing capacity to match. This method is unsafe as it can
+ /// expose uninitialized memory when len is greater than the current
+ /// length of the string.
+ pub unsafe fn set_length(&mut self, len: u32) {
+ $set_length(self, len);
+ }
+
+ /// Set the length of the string to the passed-in length, and expand
+ /// the backing capacity to match. This method is unsafe as it can
+ /// expose uninitialized memory when len is greater than the current
+ /// length of the string.
+ ///
+ /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+ pub unsafe fn fallible_set_length(&mut self, len: u32) -> Result<(), ()> {
+ if $fallible_set_length(self, len) {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ pub fn truncate(&mut self) {
+ unsafe {
+ self.set_length(0);
+ }
+ }
+
+ /// Get a `&mut` reference to the backing data for this string.
+ /// This method will allocate and copy if the current backing buffer
+ /// is immutable or shared.
+ pub fn to_mut(&mut self) -> &mut [$char_t] {
+ unsafe {
+ let len = self.len();
+ if len == 0 {
+ // Use an arbitrary but aligned non-null value as the pointer
+ slice::from_raw_parts_mut(ptr::NonNull::<$char_t>::dangling().as_ptr(), 0)
+ } else {
+ slice::from_raw_parts_mut($begin_writing(self), len)
+ }
+ }
+ }
+
+ /// Get a `&mut` reference to the backing data for this string.
+ /// This method will allocate and copy if the current backing buffer
+ /// is immutable or shared.
+ ///
+ /// Returns `Ok(&mut [T])` on success, and `Err(())` if the
+ /// allocation failed.
+ pub fn fallible_to_mut(&mut self) -> Result<&mut [$char_t], ()> {
+ unsafe {
+ let len = self.len();
+ if len == 0 {
+ // Use an arbitrary but aligned non-null value as the pointer
+ Ok(slice::from_raw_parts_mut(
+ ptr::NonNull::<$char_t>::dangling().as_ptr() as *mut $char_t, 0))
+ } else {
+ let ptr = $fallible_begin_writing(self);
+ if ptr.is_null() {
+ Err(())
+ } else {
+ Ok(slice::from_raw_parts_mut(ptr, len))
+ }
+ }
+ }
+ }
+
+ /// Unshares the buffer of the string and returns a handle
+ /// from which a writable slice whose length is the rounded-up
+ /// capacity can be obtained.
+ ///
+ /// Fails also if the new length doesn't fit in 32 bits.
+ ///
+ /// # Safety
+ ///
+ /// Unsafe because of exposure of uninitialized memory.
+ pub unsafe fn bulk_write(&mut self,
+ capacity: usize,
+ units_to_preserve: usize,
+ allow_shrinking: bool) -> Result<$BulkWriteHandle, ()> {
+ let capacity =
+ self.start_bulk_write_impl(capacity, units_to_preserve, allow_shrinking)?;
+ Ok($BulkWriteHandle::new(self, capacity))
+ }
+
+ unsafe fn start_bulk_write_impl(&mut self,
+ capacity: usize,
+ units_to_preserve: usize,
+ allow_shrinking: bool) -> Result<usize, ()> {
+ if capacity > u32::MAX as usize {
+ Err(())
+ } else {
+ let capacity32 = capacity as u32;
+ let rounded = $start_bulk_write(self,
+ capacity32,
+ units_to_preserve as u32,
+ allow_shrinking && capacity > SHRINKING_THRESHOLD);
+ if rounded == u32::MAX {
+ return Err(())
+ }
+ Ok(rounded as usize)
+ }
+ }
+
+ fn as_repr(&self) -> &$StringRepr {
+ // All $AString values point to a struct prefix which is
+ // identical to $StringRepr, thus we can cast `self`
+ // into *const $StringRepr to get the reference to the
+ // underlying data.
+ unsafe {
+ &*(self as *const _ as *const $StringRepr)
+ }
+ }
+
+ fn as_repr_mut(&mut self) -> ptr::NonNull<$StringRepr> {
+ unsafe { ptr::NonNull::new_unchecked(self as *mut _ as *mut $StringRepr)}
+ }
+
+ fn as_auto_string_repr(&self) -> Option<&$AutoStringRepr> {
+ if !self.as_repr().classflags.contains(ClassFlags::INLINE) {
+ return None;
+ }
+
+ unsafe {
+ Some(&*(self as *const _ as *const $AutoStringRepr))
+ }
+ }
+
+ /// If this is an autostring, returns the capacity (excluding the
+ /// zero terminator) of the inline buffer within `Some()`. Otherwise
+ /// returns `None`.
+ pub fn inline_capacity(&self) -> Option<usize> {
+ Some(self.as_auto_string_repr()?.inline_capacity as usize)
+ }
+ }
+
+ impl Deref for $AString {
+ type Target = [$char_t];
+ fn deref(&self) -> &[$char_t] {
+ unsafe {
+ // All $AString values point to a struct prefix which is
+ // identical to $StringRepr, thus we can cast `self`
+ // into *const $StringRepr to get the reference to the
+ // underlying data.
+ let this = &*(self as *const _ as *const $StringRepr);
+ slice::from_raw_parts(this.data.as_ptr(), this.length as usize)
+ }
+ }
+ }
+
+ impl AsRef<[$char_t]> for $AString {
+ fn as_ref(&self) -> &[$char_t] {
+ self
+ }
+ }
+
+ impl cmp::PartialEq for $AString {
+ fn eq(&self, other: &$AString) -> bool {
+ &self[..] == &other[..]
+ }
+ }
+
+ impl cmp::PartialEq<[$char_t]> for $AString {
+ fn eq(&self, other: &[$char_t]) -> bool {
+ &self[..] == other
+ }
+ }
+
+ impl cmp::PartialEq<$String> for $AString {
+ fn eq(&self, other: &$String) -> bool {
+ self.eq(&**other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<$Str<'a>> for $AString {
+ fn eq(&self, other: &$Str<'a>) -> bool {
+ self.eq(&**other)
+ }
+ }
+
+ #[repr(C)]
+ pub struct $Str<'a> {
+ hdr: $StringRepr,
+ _marker: PhantomData<&'a [$char_t]>,
+ }
+
+ impl $Str<'static> {
+ pub fn new() -> $Str<'static> {
+ $Str {
+ hdr: $StringRepr::new(ClassFlags::empty()),
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ impl<'a> Drop for $Str<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ $drop(&mut **self);
+ }
+ }
+ }
+
+ impl<'a> Deref for $Str<'a> {
+ type Target = $AString;
+ fn deref(&self) -> &$AString {
+ &self.hdr
+ }
+ }
+
+ impl<'a> DerefMut for $Str<'a> {
+ fn deref_mut(&mut self) -> &mut $AString {
+ &mut self.hdr
+ }
+ }
+
+ impl<'a> AsRef<[$char_t]> for $Str<'a> {
+ fn as_ref(&self) -> &[$char_t] {
+ &self
+ }
+ }
+
+ impl<'a> From<&'a [$char_t]> for $Str<'a> {
+ fn from(s: &'a [$char_t]) -> $Str<'a> {
+ assert!(s.len() < (u32::MAX as usize));
+ if s.is_empty() {
+ return $Str::new();
+ }
+ $Str {
+ hdr: $StringRepr {
+ data: unsafe { ptr::NonNull::new_unchecked(s.as_ptr() as *mut _) },
+ length: s.len() as u32,
+ dataflags: DataFlags::empty(),
+ classflags: ClassFlags::empty(),
+ },
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ impl<'a> From<&'a Vec<$char_t>> for $Str<'a> {
+ fn from(s: &'a Vec<$char_t>) -> $Str<'a> {
+ $Str::from(&s[..])
+ }
+ }
+
+ impl<'a> From<&'a $AString> for $Str<'a> {
+ fn from(s: &'a $AString) -> $Str<'a> {
+ $Str::from(&s[..])
+ }
+ }
+
+ impl<'a> fmt::Write for $Str<'a> {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ $AString::write_str(self, s)
+ }
+ }
+
+ impl<'a> fmt::Display for $Str<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Display>::fmt(self, f)
+ }
+ }
+
+ impl<'a> fmt::Debug for $Str<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Debug>::fmt(self, f)
+ }
+ }
+
+ impl<'a> cmp::PartialEq for $Str<'a> {
+ fn eq(&self, other: &$Str<'a>) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<[$char_t]> for $Str<'a> {
+ fn eq(&self, other: &[$char_t]) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $Str<'a> {
+ fn eq(&self, other: &&'b [$char_t]) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<str> for $Str<'a> {
+ fn eq(&self, other: &str) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a, 'b> cmp::PartialEq<&'b str> for $Str<'a> {
+ fn eq(&self, other: &&'b str) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ #[repr(C)]
+ pub struct $String {
+ hdr: $StringRepr,
+ }
+
+ unsafe impl Send for $String {}
+ unsafe impl Sync for $String {}
+
+ impl $String {
+ pub fn new() -> $String {
+ $String {
+ hdr: $StringRepr::new(ClassFlags::NULL_TERMINATED),
+ }
+ }
+
+ /// Converts this String into a StringRepr, which will leak if the
+ /// repr is not passed to something that knows how to free it.
+ pub fn into_repr(mut self) -> $StringRepr {
+ mem::replace(&mut self.hdr, $StringRepr::new(ClassFlags::NULL_TERMINATED))
+ }
+ }
+
+ impl Drop for $String {
+ fn drop(&mut self) {
+ unsafe {
+ $drop(&mut **self);
+ }
+ }
+ }
+
+ impl Deref for $String {
+ type Target = $AString;
+ fn deref(&self) -> &$AString {
+ &self.hdr
+ }
+ }
+
+ impl DerefMut for $String {
+ fn deref_mut(&mut self) -> &mut $AString {
+ &mut self.hdr
+ }
+ }
+
+ impl Clone for $String {
+ fn clone(&self) -> Self {
+ let mut copy = $String::new();
+ copy.assign(self);
+ copy
+ }
+ }
+
+ impl AsRef<[$char_t]> for $String {
+ fn as_ref(&self) -> &[$char_t] {
+ &self
+ }
+ }
+
+ impl<'a> From<&'a [$char_t]> for $String {
+ fn from(s: &'a [$char_t]) -> $String {
+ let mut res = $String::new();
+ res.assign(&$Str::from(&s[..]));
+ res
+ }
+ }
+
+ impl<'a> From<&'a Vec<$char_t>> for $String {
+ fn from(s: &'a Vec<$char_t>) -> $String {
+ $String::from(&s[..])
+ }
+ }
+
+ impl<'a> From<&'a $AString> for $String {
+ fn from(s: &'a $AString) -> $String {
+ $String::from(&s[..])
+ }
+ }
+
+ impl From<Box<[$char_t]>> for $String {
+ fn from(s: Box<[$char_t]>) -> $String {
+ s.into_vec().into()
+ }
+ }
+
+ impl From<Vec<$char_t>> for $String {
+ fn from(mut s: Vec<$char_t>) -> $String {
+ assert!(s.len() < (u32::MAX as usize));
+ if s.is_empty() {
+ return $String::new();
+ }
+
+ let length = s.len() as u32;
+ s.push(0); // null terminator
+
+ // SAFETY NOTE: This method produces an data_flags::OWNED
+ // ns[C]String from a Box<[$char_t]>. this is only safe
+ // because in the Gecko tree, we use the same allocator for
+ // Rust code as for C++ code, meaning that our box can be
+ // legally freed with libc::free().
+ let ptr = s.as_mut_ptr();
+ mem::forget(s);
+ unsafe {
+ Gecko_IncrementStringAdoptCount(ptr as *mut _);
+ }
+ $String {
+ hdr: $StringRepr {
+ data: unsafe { ptr::NonNull::new_unchecked(ptr) },
+ length,
+ dataflags: DataFlags::OWNED | DataFlags::TERMINATED,
+ classflags: ClassFlags::NULL_TERMINATED,
+ }
+ }
+ }
+ }
+
+ impl fmt::Write for $String {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ $AString::write_str(self, s)
+ }
+ }
+
+ impl fmt::Display for $String {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Display>::fmt(self, f)
+ }
+ }
+
+ impl fmt::Debug for $String {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Debug>::fmt(self, f)
+ }
+ }
+
+ impl cmp::PartialEq for $String {
+ fn eq(&self, other: &$String) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl cmp::PartialEq<[$char_t]> for $String {
+ fn eq(&self, other: &[$char_t]) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<&'a [$char_t]> for $String {
+ fn eq(&self, other: &&'a [$char_t]) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ impl cmp::PartialEq<str> for $String {
+ fn eq(&self, other: &str) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<&'a str> for $String {
+ fn eq(&self, other: &&'a str) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ /// An adapter type to allow for passing both types which coerce to
+ /// &[$char_type], and &$AString to a function, while still performing
+ /// optimized operations when passed the $AString.
+ pub enum $StringAdapter<'a> {
+ Borrowed($Str<'a>),
+ Abstract(&'a $AString),
+ }
+
+ impl<'a> $StringAdapter<'a> {
+ fn as_ptr(&self) -> *const $AString {
+ &**self
+ }
+ }
+
+ impl<'a> Deref for $StringAdapter<'a> {
+ type Target = $AString;
+
+ fn deref(&self) -> &$AString {
+ match *self {
+ $StringAdapter::Borrowed(ref s) => s,
+ $StringAdapter::Abstract(ref s) => s,
+ }
+ }
+ }
+
+ impl<'a> $StringAdapter<'a> {
+ #[allow(dead_code)]
+ fn is_abstract(&self) -> bool {
+ match *self {
+ $StringAdapter::Borrowed(_) => false,
+ $StringAdapter::Abstract(_) => true,
+ }
+ }
+ }
+
+ string_like! {
+ char_t = $char_t;
+
+ AString = $AString;
+ String = $String;
+ Str = $Str;
+
+ StringLike = $StringLike;
+ StringAdapter = $StringAdapter;
+ }
+ }
+}
+
+///////////////////////////////////////////
+// Bindings for nsCString (u8 char type) //
+///////////////////////////////////////////
+
+define_string_types! {
+ char_t = u8;
+
+ AString = nsACString;
+ String = nsCString;
+ Str = nsCStr;
+
+ StringLike = nsCStringLike;
+ StringAdapter = nsCStringAdapter;
+
+ StringRepr = nsCStringRepr;
+ AutoStringRepr = nsAutoCStringRepr;
+
+ BulkWriteHandle = nsACStringBulkWriteHandle;
+
+ drop = Gecko_FinalizeCString;
+ assign = Gecko_AssignCString, Gecko_FallibleAssignCString;
+ take_from = Gecko_TakeFromCString, Gecko_FallibleTakeFromCString;
+ append = Gecko_AppendCString, Gecko_FallibleAppendCString;
+ set_length = Gecko_SetLengthCString, Gecko_FallibleSetLengthCString;
+ begin_writing = Gecko_BeginWritingCString, Gecko_FallibleBeginWritingCString;
+ start_bulk_write = Gecko_StartBulkWriteCString;
+}
+
+impl nsACString {
+ /// Gets a CString as an utf-8 str or a String, trying to avoid copies, and
+ /// replacing invalid unicode sequences with replacement characters.
+ #[inline]
+ pub fn to_utf8(&self) -> borrow::Cow<str> {
+ String::from_utf8_lossy(&self[..])
+ }
+
+ #[inline]
+ pub unsafe fn as_str_unchecked(&self) -> &str {
+ if cfg!(debug_assertions) {
+ str::from_utf8(self).expect("Should be utf-8")
+ } else {
+ str::from_utf8_unchecked(self)
+ }
+ }
+}
+
+impl<'a> From<&'a str> for nsCStr<'a> {
+ fn from(s: &'a str) -> nsCStr<'a> {
+ s.as_bytes().into()
+ }
+}
+
+impl<'a> From<&'a String> for nsCStr<'a> {
+ fn from(s: &'a String) -> nsCStr<'a> {
+ nsCStr::from(&s[..])
+ }
+}
+
+impl<'a> From<&'a str> for nsCString {
+ fn from(s: &'a str) -> nsCString {
+ s.as_bytes().into()
+ }
+}
+
+impl<'a> From<&'a String> for nsCString {
+ fn from(s: &'a String) -> nsCString {
+ nsCString::from(&s[..])
+ }
+}
+
+impl From<Box<str>> for nsCString {
+ fn from(s: Box<str>) -> nsCString {
+ s.into_string().into()
+ }
+}
+
+impl From<String> for nsCString {
+ fn from(s: String) -> nsCString {
+ s.into_bytes().into()
+ }
+}
+
+// Support for the write!() macro for appending to nsACStrings
+impl fmt::Write for nsACString {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ self.append(s);
+ Ok(())
+ }
+}
+
+impl fmt::Display for nsACString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Display::fmt(&self.to_utf8(), f)
+ }
+}
+
+impl fmt::Debug for nsACString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Debug::fmt(&self.to_utf8(), f)
+ }
+}
+
+impl cmp::PartialEq<str> for nsACString {
+ fn eq(&self, other: &str) -> bool {
+ &self[..] == other.as_bytes()
+ }
+}
+
+impl nsCStringLike for str {
+ fn adapt(&self) -> nsCStringAdapter {
+ nsCStringAdapter::Borrowed(nsCStr::from(self))
+ }
+}
+
+impl nsCStringLike for String {
+ fn adapt(&self) -> nsCStringAdapter {
+ nsCStringAdapter::Borrowed(nsCStr::from(&self[..]))
+ }
+}
+
+impl nsCStringLike for Box<str> {
+ fn adapt(&self) -> nsCStringAdapter {
+ nsCStringAdapter::Borrowed(nsCStr::from(&self[..]))
+ }
+}
+
+// This trait is implemented on types which are Latin1 `nsCString`-like,
+// in that they can at very low cost be converted to a borrowed
+// `&nsACString` and do not denote UTF-8ness in the Rust type system.
+//
+// This trait is used to DWIM when calling the methods on
+// `nsACString`.
+string_like! {
+ char_t = u8;
+
+ AString = nsACString;
+ String = nsCString;
+ Str = nsCStr;
+
+ StringLike = Latin1StringLike;
+ StringAdapter = nsCStringAdapter;
+}
+
+///////////////////////////////////////////
+// Bindings for nsString (u16 char type) //
+///////////////////////////////////////////
+
+define_string_types! {
+ char_t = u16;
+
+ AString = nsAString;
+ String = nsString;
+ Str = nsStr;
+
+ StringLike = nsStringLike;
+ StringAdapter = nsStringAdapter;
+
+ StringRepr = nsStringRepr;
+ AutoStringRepr = nsAutoStringRepr;
+
+ BulkWriteHandle = nsAStringBulkWriteHandle;
+
+ drop = Gecko_FinalizeString;
+ assign = Gecko_AssignString, Gecko_FallibleAssignString;
+ take_from = Gecko_TakeFromString, Gecko_FallibleTakeFromString;
+ append = Gecko_AppendString, Gecko_FallibleAppendString;
+ set_length = Gecko_SetLengthString, Gecko_FallibleSetLengthString;
+ begin_writing = Gecko_BeginWritingString, Gecko_FallibleBeginWritingString;
+ start_bulk_write = Gecko_StartBulkWriteString;
+}
+
+// NOTE: The From impl for a string slice for nsString produces a <'static>
+// lifetime, as it allocates.
+impl<'a> From<&'a str> for nsString {
+ fn from(s: &'a str) -> nsString {
+ s.encode_utf16().collect::<Vec<u16>>().into()
+ }
+}
+
+impl<'a> From<&'a String> for nsString {
+ fn from(s: &'a String) -> nsString {
+ nsString::from(&s[..])
+ }
+}
+
+// Support for the write!() macro for writing to nsStrings
+impl fmt::Write for nsAString {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ // Directly invoke gecko's routines for appending utf8 strings to
+ // nsAString values, to avoid as much overhead as possible
+ self.append_str(s);
+ Ok(())
+ }
+}
+
+impl nsAString {
+ /// Turns this utf-16 string into a string, replacing invalid unicode
+ /// sequences with replacement characters.
+ ///
+ /// This is needed because the default ToString implementation goes through
+ /// fmt::Display, and thus allocates the string twice.
+ #[allow(clippy::inherent_to_string_shadow_display)]
+ pub fn to_string(&self) -> String {
+ String::from_utf16_lossy(&self[..])
+ }
+}
+
+impl fmt::Display for nsAString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Display::fmt(&self.to_string(), f)
+ }
+}
+
+impl fmt::Debug for nsAString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Debug::fmt(&self.to_string(), f)
+ }
+}
+
+impl cmp::PartialEq<str> for nsAString {
+ fn eq(&self, other: &str) -> bool {
+ other.encode_utf16().eq(self.iter().cloned())
+ }
+}
+
+#[cfg(not(feature = "gecko_debug"))]
+#[allow(non_snake_case)]
+unsafe fn Gecko_IncrementStringAdoptCount(_: *mut c_void) {}
+
+extern "C" {
+ #[cfg(feature = "gecko_debug")]
+ fn Gecko_IncrementStringAdoptCount(data: *mut c_void);
+
+ // Gecko implementation in nsSubstring.cpp
+ fn Gecko_FinalizeCString(this: *mut nsACString);
+
+ fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString);
+ fn Gecko_TakeFromCString(this: *mut nsACString, other: *mut nsACString);
+ fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString);
+ fn Gecko_SetLengthCString(this: *mut nsACString, length: u32);
+ fn Gecko_BeginWritingCString(this: *mut nsACString) -> *mut u8;
+ fn Gecko_FallibleAssignCString(this: *mut nsACString, other: *const nsACString) -> bool;
+ fn Gecko_FallibleTakeFromCString(this: *mut nsACString, other: *mut nsACString) -> bool;
+ fn Gecko_FallibleAppendCString(this: *mut nsACString, other: *const nsACString) -> bool;
+ fn Gecko_FallibleSetLengthCString(this: *mut nsACString, length: u32) -> bool;
+ fn Gecko_FallibleBeginWritingCString(this: *mut nsACString) -> *mut u8;
+ fn Gecko_StartBulkWriteCString(
+ this: *mut nsACString,
+ capacity: u32,
+ units_to_preserve: u32,
+ allow_shrinking: bool,
+ ) -> u32;
+
+ fn Gecko_FinalizeString(this: *mut nsAString);
+
+ fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString);
+ fn Gecko_TakeFromString(this: *mut nsAString, other: *mut nsAString);
+ fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString);
+ fn Gecko_SetLengthString(this: *mut nsAString, length: u32);
+ fn Gecko_BeginWritingString(this: *mut nsAString) -> *mut u16;
+ fn Gecko_FallibleAssignString(this: *mut nsAString, other: *const nsAString) -> bool;
+ fn Gecko_FallibleTakeFromString(this: *mut nsAString, other: *mut nsAString) -> bool;
+ fn Gecko_FallibleAppendString(this: *mut nsAString, other: *const nsAString) -> bool;
+ fn Gecko_FallibleSetLengthString(this: *mut nsAString, length: u32) -> bool;
+ fn Gecko_FallibleBeginWritingString(this: *mut nsAString) -> *mut u16;
+ fn Gecko_StartBulkWriteString(
+ this: *mut nsAString,
+ capacity: u32,
+ units_to_preserve: u32,
+ allow_shrinking: bool,
+ ) -> u32;
+}
+
+//////////////////////////////////////
+// Repr Validation Helper Functions //
+//////////////////////////////////////
+
+pub mod test_helpers {
+ //! This module only exists to help with ensuring that the layout of the
+ //! structs inside of rust and C++ are identical.
+ //!
+ //! It is public to ensure that these testing functions are avaliable to
+ //! gtest code.
+
+ use super::{nsACString, nsAString};
+ use super::{nsCStr, nsCString, nsCStringRepr};
+ use super::{nsStr, nsString, nsStringRepr};
+ use super::{ClassFlags, DataFlags};
+ use std::mem;
+
+ /// Generates an #[no_mangle] extern "C" function which returns the size and
+ /// alignment of the given type with the given name.
+ macro_rules! size_align_check {
+ ($T:ty, $fname:ident) => {
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn $fname(size: *mut usize, align: *mut usize) {
+ *size = mem::size_of::<$T>();
+ *align = mem::align_of::<$T>();
+ }
+ };
+ ($T:ty, $U:ty, $V:ty, $fname:ident) => {
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn $fname(size: *mut usize, align: *mut usize) {
+ *size = mem::size_of::<$T>();
+ *align = mem::align_of::<$T>();
+
+ assert_eq!(*size, mem::size_of::<$U>());
+ assert_eq!(*align, mem::align_of::<$U>());
+ assert_eq!(*size, mem::size_of::<$V>());
+ assert_eq!(*align, mem::align_of::<$V>());
+ }
+ };
+ }
+
+ size_align_check!(
+ nsStringRepr,
+ nsString,
+ nsStr<'static>,
+ Rust_Test_ReprSizeAlign_nsString
+ );
+ size_align_check!(
+ nsCStringRepr,
+ nsCString,
+ nsCStr<'static>,
+ Rust_Test_ReprSizeAlign_nsCString
+ );
+
+ /// Generates a $[no_mangle] extern "C" function which returns the size,
+ /// alignment and offset in the parent struct of a given member, with the
+ /// given name.
+ ///
+ /// This method can trigger Undefined Behavior if the accessing the member
+ /// $member on a given type would use that type's `Deref` implementation.
+ macro_rules! member_check {
+ ($T:ty, $U:ty, $V:ty, $member:ident, $method:ident) => {
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn $method(
+ size: *mut usize,
+ align: *mut usize,
+ offset: *mut usize,
+ ) {
+ // Create a temporary value of type T to get offsets, sizes
+ // and alignments from.
+ let tmp: mem::MaybeUninit<$T> = mem::MaybeUninit::uninit();
+ // FIXME: This should use &raw references when available,
+ // this is technically UB as it creates a reference to
+ // uninitialized memory, but there's no better way to do
+ // this right now.
+ let tmp = &*tmp.as_ptr();
+ *size = mem::size_of_val(&tmp.$member);
+ *align = mem::align_of_val(&tmp.$member);
+ *offset = (&tmp.$member as *const _ as usize) - (tmp as *const $T as usize);
+
+ let tmp: mem::MaybeUninit<$U> = mem::MaybeUninit::uninit();
+ let tmp = &*tmp.as_ptr();
+ assert_eq!(*size, mem::size_of_val(&tmp.hdr.$member));
+ assert_eq!(*align, mem::align_of_val(&tmp.hdr.$member));
+ assert_eq!(
+ *offset,
+ (&tmp.hdr.$member as *const _ as usize) - (tmp as *const $U as usize)
+ );
+
+ let tmp: mem::MaybeUninit<$V> = mem::MaybeUninit::uninit();
+ let tmp = &*tmp.as_ptr();
+ assert_eq!(*size, mem::size_of_val(&tmp.hdr.$member));
+ assert_eq!(*align, mem::align_of_val(&tmp.hdr.$member));
+ assert_eq!(
+ *offset,
+ (&tmp.hdr.$member as *const _ as usize) - (tmp as *const $V as usize)
+ );
+ }
+ };
+ }
+
+ member_check!(
+ nsStringRepr,
+ nsString,
+ nsStr<'static>,
+ data,
+ Rust_Test_Member_nsString_mData
+ );
+ member_check!(
+ nsStringRepr,
+ nsString,
+ nsStr<'static>,
+ length,
+ Rust_Test_Member_nsString_mLength
+ );
+ member_check!(
+ nsStringRepr,
+ nsString,
+ nsStr<'static>,
+ dataflags,
+ Rust_Test_Member_nsString_mDataFlags
+ );
+ member_check!(
+ nsStringRepr,
+ nsString,
+ nsStr<'static>,
+ classflags,
+ Rust_Test_Member_nsString_mClassFlags
+ );
+ member_check!(
+ nsCStringRepr,
+ nsCString,
+ nsCStr<'static>,
+ data,
+ Rust_Test_Member_nsCString_mData
+ );
+ member_check!(
+ nsCStringRepr,
+ nsCString,
+ nsCStr<'static>,
+ length,
+ Rust_Test_Member_nsCString_mLength
+ );
+ member_check!(
+ nsCStringRepr,
+ nsCString,
+ nsCStr<'static>,
+ dataflags,
+ Rust_Test_Member_nsCString_mDataFlags
+ );
+ member_check!(
+ nsCStringRepr,
+ nsCString,
+ nsCStr<'static>,
+ classflags,
+ Rust_Test_Member_nsCString_mClassFlags
+ );
+
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn Rust_Test_NsStringFlags(
+ f_terminated: *mut u16,
+ f_voided: *mut u16,
+ f_refcounted: *mut u16,
+ f_owned: *mut u16,
+ f_inline: *mut u16,
+ f_literal: *mut u16,
+ f_class_inline: *mut u16,
+ f_class_null_terminated: *mut u16,
+ ) {
+ *f_terminated = DataFlags::TERMINATED.bits();
+ *f_voided = DataFlags::VOIDED.bits();
+ *f_refcounted = DataFlags::REFCOUNTED.bits();
+ *f_owned = DataFlags::OWNED.bits();
+ *f_inline = DataFlags::INLINE.bits();
+ *f_literal = DataFlags::LITERAL.bits();
+ *f_class_inline = ClassFlags::INLINE.bits();
+ *f_class_null_terminated = ClassFlags::NULL_TERMINATED.bits();
+ }
+
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn Rust_InlineCapacityFromRust(
+ cstring: *const nsACString,
+ string: *const nsAString,
+ cstring_capacity: *mut usize,
+ string_capacity: *mut usize,
+ ) {
+ *cstring_capacity = (*cstring).inline_capacity().unwrap();
+ *string_capacity = (*string).inline_capacity().unwrap();
+ }
+}
diff --git a/xpcom/rust/xpcom/Cargo.toml b/xpcom/rust/xpcom/Cargo.toml
new file mode 100644
index 0000000000..9c8a1ec83b
--- /dev/null
+++ b/xpcom/rust/xpcom/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "xpcom"
+version = "0.1.0"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+cstr = "0.2"
+libc = "0.2"
+nsstring = { path = "../nsstring" }
+nserror = { path = "../nserror" }
+threadbound = "0.1"
+xpcom_macros = { path = "xpcom_macros" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+mozbuild = "0.1"
+
+[features]
+thread_sanitizer = []
+gecko_refcount_logging = []
diff --git a/xpcom/rust/xpcom/src/base.rs b/xpcom/rust/xpcom/src/base.rs
new file mode 100644
index 0000000000..556768a179
--- /dev/null
+++ b/xpcom/rust/xpcom/src/base.rs
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::interfaces::{nsIInterfaceRequestor, nsISupports};
+use crate::{GetterAddrefs, RefCounted, RefPtr};
+
+#[repr(C)]
+#[derive(Copy, Clone, Eq, PartialEq)]
+/// A "unique identifier". This is modeled after OSF DCE UUIDs.
+pub struct nsID(pub u32, pub u16, pub u16, pub [u8; 8]);
+
+/// Interface IDs
+pub type nsIID = nsID;
+/// Class IDs
+pub type nsCID = nsID;
+
+/// A type which implements XpCom must follow the following rules:
+///
+/// * It must be a legal XPCOM interface.
+/// * The result of a QueryInterface or similar call, passing IID, must return a
+/// valid reference to an object of the given type.
+/// * It must be valid to cast a &self reference to a &nsISupports reference.
+pub unsafe trait XpCom: RefCounted {
+ const IID: nsIID;
+
+ /// Perform a QueryInterface call on this object, attempting to dynamically
+ /// cast it to the requested interface type. Returns Some(RefPtr<T>) if the
+ /// cast succeeded, and None otherwise.
+ fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>> {
+ let mut ga = GetterAddrefs::<T>::new();
+ unsafe {
+ if (*(self as *const Self as *const nsISupports))
+ .QueryInterface(&T::IID, ga.void_ptr())
+ .succeeded()
+ {
+ ga.refptr()
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Perform a `GetInterface` call on this object, returning `None` if the
+ /// object doesn't implement `nsIInterfaceRequestor`, or can't access the
+ /// interface `T`.
+ fn get_interface<T: XpCom>(&self) -> Option<RefPtr<T>> {
+ let ireq = self.query_interface::<nsIInterfaceRequestor>()?;
+
+ let mut ga = GetterAddrefs::<T>::new();
+ unsafe {
+ if ireq.GetInterface(&T::IID, ga.void_ptr()).succeeded() {
+ ga.refptr()
+ } else {
+ None
+ }
+ }
+ }
+}
diff --git a/xpcom/rust/xpcom/src/components.rs b/xpcom/rust/xpcom/src/components.rs
new file mode 100644
index 0000000000..c83e2df705
--- /dev/null
+++ b/xpcom/rust/xpcom/src/components.rs
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module contains convenient accessors for static XPCOM components.
+//!
+//! The contents of this file are generated from
+//! `xpcom/components/gen_static_components.py`.
+
+extern "C" {
+ fn Gecko_GetServiceByModuleID(
+ id: ModuleID,
+ iid: &crate::nsIID,
+ result: *mut *mut libc::c_void,
+ ) -> nserror::nsresult;
+ fn Gecko_CreateInstanceByModuleID(
+ id: ModuleID,
+ iid: &crate::nsIID,
+ result: *mut *mut libc::c_void,
+ ) -> nserror::nsresult;
+}
+
+include!(mozbuild::objdir_path!("xpcom/components/components.rs"));
diff --git a/xpcom/rust/xpcom/src/interfaces/idl.rs b/xpcom/rust/xpcom/src/interfaces/idl.rs
new file mode 100644
index 0000000000..c8d62ce716
--- /dev/null
+++ b/xpcom/rust/xpcom/src/interfaces/idl.rs
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(bad_style)]
+
+use crate::interfaces::*;
+use crate::*;
+
+// NOTE: This file contains a series of `include!()` invocations, defining all
+// idl interfaces directly within this module.
+include!(mozbuild::objdir_path!("dist/xpcrs/rt/all.rs"));
diff --git a/xpcom/rust/xpcom/src/interfaces/mod.rs b/xpcom/rust/xpcom/src/interfaces/mod.rs
new file mode 100644
index 0000000000..5b9de5cef5
--- /dev/null
+++ b/xpcom/rust/xpcom/src/interfaces/mod.rs
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module contains the xpcom interfaces exposed to rust code.
+//!
+//! The items in this module come in a few flavours:
+//!
+//! 1. `nsI*`: These are the types for XPCOM interfaces. They should always be
+//! passed behind a reference, pointer, or `RefPtr`. They may be coerced to
+//! their base interfaces using the `coerce` method.
+//!
+//! 2. `nsI*Coerce`: These traits provide the implementation mechanics for the
+//! `coerce` method, and can usually be ignored. *These traits are hidden in
+//! rustdoc*
+//!
+//! 3. `nsI*VTable`: These structs are the vtable definitions for each type.
+//! They contain the base interface's vtable, followed by pointers for each
+//! of the vtable's methods. If direct access is needed, a `*const nsI*` can
+//! be safely transmuted to a `*const nsI*VTable`. *These structs are hidden
+//! in rustdoc*
+//!
+//! 4. Typedefs used in idl file definitions.
+
+// Interfaces defined in .idl files
+mod idl;
+pub use self::idl::*;
+
+// Other interfaces which are needed to compile
+mod nonidl;
+pub use self::nonidl::*;
diff --git a/xpcom/rust/xpcom/src/interfaces/nonidl.rs b/xpcom/rust/xpcom/src/interfaces/nonidl.rs
new file mode 100644
index 0000000000..b9e3f1abe2
--- /dev/null
+++ b/xpcom/rust/xpcom/src/interfaces/nonidl.rs
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module contains definitions of interfaces which are used in idl files
+//! as forward declarations, but are not actually defined in an idl file.
+//!
+//! NOTE: The IIDs in these files must be kept in sync with the IDL definitions
+//! in the corresponding C++ files.
+
+use crate::nsID;
+
+// XXX: This macro should have an option for a custom base interface instead of
+// nsISupports, such that Document can have nsINode as a base, etc. For now,
+// query_interface should be sufficient.
+macro_rules! nonidl {
+ ($name:ident, $iid:expr) => {
+ /// This interface is referenced from idl files, but not defined in
+ /// them. It exports no methods to rust code.
+ #[repr(C)]
+ pub struct $name {
+ _vtable: *const $crate::interfaces::nsISupportsVTable,
+ }
+
+ unsafe impl $crate::XpCom for $name {
+ const IID: $crate::nsIID = $iid;
+ }
+
+ unsafe impl $crate::RefCounted for $name {
+ #[inline]
+ unsafe fn addref(&self) {
+ self.AddRef();
+ }
+ #[inline]
+ unsafe fn release(&self) {
+ self.Release();
+ }
+ }
+
+ impl ::std::ops::Deref for $name {
+ type Target = $crate::interfaces::nsISupports;
+ #[inline]
+ fn deref(&self) -> &$crate::interfaces::nsISupports {
+ unsafe { ::std::mem::transmute(self) }
+ }
+ }
+ };
+}
+
+// Must be kept in sync with Document.h
+nonidl!(
+ Document,
+ nsID(
+ 0xce1f7627,
+ 0x7109,
+ 0x4977,
+ [0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa]
+ )
+);
+
+// Must be kept in sync with nsINode.h
+nonidl!(
+ nsINode,
+ nsID(
+ 0x70ba4547,
+ 0x7699,
+ 0x44fc,
+ [0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a]
+ )
+);
+
+// Must be kept in sync with nsIContent.h
+nonidl!(
+ nsIContent,
+ nsID(
+ 0x8e1bab9d,
+ 0x8815,
+ 0x4d2c,
+ [0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22]
+ )
+);
+
+// Must be kept in sync with nsIConsoleReportCollector.h
+nonidl!(
+ nsIConsoleReportCollector,
+ nsID(
+ 0xdd98a481,
+ 0xd2c4,
+ 0x4203,
+ [0x8d, 0xfa, 0x85, 0xbf, 0xd7, 0xdc, 0xd7, 0x05]
+ )
+);
+
+// Must be kept in sync with nsIGlobalObject.h
+nonidl!(
+ nsIGlobalObject,
+ nsID(
+ 0x11afa8be,
+ 0xd997,
+ 0x4e07,
+ [0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f]
+ )
+);
+
+// Must be kept in sync with nsIScriptElement.h
+nonidl!(
+ nsIScriptElement,
+ nsID(
+ 0xe60fca9b,
+ 0x1b96,
+ 0x4e4e,
+ [0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c]
+ )
+);
+
+// Must be kept in sync with nsPIDOMWindow.h
+nonidl!(
+ nsPIDOMWindowOuter,
+ nsID(
+ 0x769693d4,
+ 0xb009,
+ 0x4fe2,
+ [0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf]
+ )
+);
+
+// Must be kept in sync with nsPIDOMWindow.h
+nonidl!(
+ nsPIDOMWindowInner,
+ nsID(
+ 0x775dabc9,
+ 0x8f43,
+ 0x4277,
+ [0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb]
+ )
+);
+
+// Must be kept in sync with nsIScriptContext.h
+nonidl!(
+ nsIScriptContext,
+ nsID(
+ 0x54cbe9cf,
+ 0x7282,
+ 0x421a,
+ [0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0]
+ )
+);
+
+// Must be kept in sync with nsIScriptGlobalObject.h
+nonidl!(
+ nsIScriptGlobalObject,
+ nsID(
+ 0x876f83bd,
+ 0x6314,
+ 0x460a,
+ [0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1]
+ )
+);
+
+// Must be kept in sync with nsIScrollObserver.h
+nonidl!(
+ nsIScrollObserver,
+ nsID(
+ 0xaa5026eb,
+ 0x2f88,
+ 0x4026,
+ [0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00]
+ )
+);
+
+// Must be kept in sync with nsIWidget.h
+nonidl!(
+ nsIWidget,
+ nsID(
+ 0x06396bf6,
+ 0x2dd8,
+ 0x45e5,
+ [0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80]
+ )
+);
diff --git a/xpcom/rust/xpcom/src/lib.rs b/xpcom/rust/xpcom/src/lib.rs
new file mode 100644
index 0000000000..ac039ebe76
--- /dev/null
+++ b/xpcom/rust/xpcom/src/lib.rs
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This crate contains the functionality required in order to both implement
+//! and call XPCOM methods from rust code.
+//!
+//! For documentation on how to implement XPCOM methods, see the documentation
+//! for the [`xpcom_macros`](../xpcom_macros/index.html) crate.
+
+#![allow(non_snake_case)]
+#![allow(non_camel_case_types)]
+
+// re-export the xpcom_macros macro
+pub use xpcom_macros::xpcom;
+
+// Helper functions and data structures are exported in the root of the crate.
+mod base;
+pub use base::*;
+
+// Declarative macro to generate XPCOM method stubs.
+mod method;
+pub use method::*;
+
+// dom::Promise resolving.
+mod promise;
+pub use promise::*;
+
+mod refptr;
+pub use refptr::*;
+
+mod statics;
+pub use statics::*;
+
+// XPCOM interface definitions.
+pub mod interfaces;
+
+// XPCOM component getters.
+pub mod components;
+
+// Implementation details of the xpcom_macros crate.
+#[doc(hidden)]
+pub mod reexports;
diff --git a/xpcom/rust/xpcom/src/method.rs b/xpcom/rust/xpcom/src/method.rs
new file mode 100644
index 0000000000..66c0510bd9
--- /dev/null
+++ b/xpcom/rust/xpcom/src/method.rs
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use nserror::{nsresult, NS_ERROR_NULL_POINTER};
+
+/// The xpcom_method macro generates a Rust XPCOM method stub that converts
+/// raw pointer arguments to references, calls a Rustic implementation
+/// of the method, writes its return value into the XPCOM method's outparameter,
+/// and returns an nsresult.
+///
+/// In other words, given an XPCOM method like:
+///
+/// ```ignore
+/// interface nsIFooBarBaz : nsISupports {
+/// nsIVariant foo(in AUTF8String bar, [optional] in bool baz);
+/// }
+/// ```
+///
+/// And a Rust implementation that uses #[xpcom] to implement it:
+///
+/// ```ignore
+/// #[xpcom(implement(nsIFooBarBaz), atomic)]
+/// struct FooBarBaz {
+/// // …
+/// }
+/// ```
+///
+/// With the appropriate extern crate and use declarations
+///
+/// ```ignore
+/// extern crate xpcom;
+/// use xpcom::xpcom_method;
+/// ```
+///
+/// Invoking the macro with the name of the XPCOM method, the name of its
+/// Rustic implementation, the set of its arguments, and its return value:
+///
+/// ```ignore
+/// impl FooBarBaz {
+/// xpcom_method!(
+/// foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant
+/// );
+/// }
+/// ```
+///
+/// Results in the macro generating an XPCOM stub like the following:
+///
+/// ```ignore
+/// unsafe fn Foo(&self, bar: *const nsACString, baz: bool, retval: *mut *const nsIVariant) -> nsresult {
+/// let bar = match Ensure::ensure(bar) {
+/// Ok(val) => val,
+/// Err(result) => return result,
+/// };
+/// let baz = match Ensure::ensure(baz) {
+/// Ok(val) => val,
+/// Err(result) => return result,
+/// };
+///
+/// match self.foo(bar, baz) {
+/// Ok(val) => {
+/// val.forget(&mut *retval);
+/// NS_OK
+/// }
+/// Err(error) => {
+/// error!("{}", error);
+/// error.into()
+/// }
+/// }
+/// }
+/// ```
+///
+/// Which calls a Rustic implementation (that you implement) like the following:
+///
+/// ```ignore
+/// impl FooBarBaz {
+/// fn foo(&self, bar: &nsACString, baz: bool) -> Result<RefPtr<nsIVariant>, nsresult> {
+/// // …
+/// }
+/// }
+/// ```
+///
+/// Notes:
+///
+/// On error, the Rustic implementation can return an Err(nsresult) or any
+/// other type that implements Into<nsresult>. So you can define and return
+/// a custom error type, which the XPCOM stub will convert to nsresult.
+///
+/// This macro assumes that all non-null pointer arguments are valid!
+/// It does ensure that they aren't null, using the `ensure_param` macro.
+/// But it doesn't otherwise check their validity. That makes the function
+/// unsafe, so callers must ensure that they only call it with valid pointer
+/// arguments.
+#[macro_export]
+macro_rules! xpcom_method {
+ // This rule is provided to ensure external modules don't need to import
+ // internal implementation details of xpcom_method.
+ // The @ensure_param rule converts raw pointer arguments to references,
+ // returning NS_ERROR_NULL_POINTER if the argument is_null().
+ //
+ // Notes:
+ //
+ // This rule can be called on a non-pointer copy parameter, but there's no
+ // benefit to doing so. The macro will just set the value of the parameter
+ // to itself. (This macro does this anyway due to limitations in declarative
+ // macros; it isn't currently possible to distinguish between pointer and
+ // copy types when processing a set of parameters.)
+ //
+ // The macro currently supports only in-parameters (*const nsIFoo); It
+ // doesn't (yet?) support out-parameters (*mut nsIFoo). The xpcom_method
+ // macro itself does, however, support the return value out-parameter.
+ (@ensure_param $name:ident) => {
+ let $name = match $crate::Ensure::ensure($name) {
+ Ok(val) => val,
+ Err(result) => return result,
+ };
+ };
+
+ // `#[allow(non_snake_case)]` is used for each method because `$xpcom_name`
+ // is almost always UpperCamelCase, and Rust gives a warning that it should
+ // be snake_case. It isn't reasonable to rename the XPCOM methods, so
+ // silence the warning.
+
+ // A method whose return value is a *mut *const nsISomething type.
+ // Example: foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant
+ ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> *const $retval:ty) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut *const $retval) -> nsresult {
+ $(xpcom_method!(@ensure_param $param_name);)*
+ match self.$rust_name($($param_name, )*) {
+ Ok(val) => {
+ val.forget(&mut *retval);
+ NS_OK
+ }
+ Err(error) => {
+ error.into()
+ }
+ }
+ }
+ };
+
+ // A method whose return value is a *mut nsAString type.
+ // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsAString
+ ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsAString) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsAString) -> nsresult {
+ $(xpcom_method!(@ensure_param $param_name);)*
+ match self.$rust_name($($param_name, )*) {
+ Ok(val) => {
+ (*retval).assign(&val);
+ NS_OK
+ }
+ Err(error) => {
+ error.into()
+ }
+ }
+ }
+ };
+
+ // A method whose return value is a *mut nsACString type.
+ // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsACString
+ ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsACString) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsACString) -> nsresult {
+ $(xpcom_method!(@ensure_param $param_name);)*
+ match self.$rust_name($($param_name, )*) {
+ Ok(val) => {
+ (*retval).assign(&val);
+ NS_OK
+ }
+ Err(error) => {
+ error.into()
+ }
+ }
+ }
+ };
+
+ // A method whose return value is a non-nsA[C]String *mut type.
+ // Example: foo => Foo(bar: *const nsACString, baz: bool) -> bool
+ ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> $retval:ty) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut $retval) -> nsresult {
+ $(xpcom_method!(@ensure_param $param_name);)*
+ match self.$rust_name($($param_name, )*) {
+ Ok(val) => {
+ *retval = val;
+ NS_OK
+ }
+ Err(error) => {
+ error.into()
+ }
+ }
+ }
+ };
+
+ // A method that doesn't have a return value.
+ // Example: foo => Foo(bar: *const nsACString, baz: bool)
+ ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*)) => {
+ #[allow(non_snake_case)]
+ unsafe fn $xpcom_name(&self, $($param_name: $param_type,)*) -> nsresult {
+ $(xpcom_method!(@ensure_param $param_name);)*
+ match self.$rust_name($($param_name, )*) {
+ Ok(_) => NS_OK,
+ Err(error) => {
+ error.into()
+ }
+ }
+ }
+ };
+}
+
+/// A trait that ensures that a raw pointer isn't null and converts it to
+/// a reference. Because of limitations in declarative macros, this includes an
+/// implementation for types that are Copy, which simply returns the value
+/// itself.
+#[doc(hidden)]
+pub trait Ensure<T> {
+ unsafe fn ensure(value: T) -> Self;
+}
+
+impl<'a, T: 'a> Ensure<*const T> for Result<&'a T, nsresult> {
+ unsafe fn ensure(ptr: *const T) -> Result<&'a T, nsresult> {
+ if ptr.is_null() {
+ Err(NS_ERROR_NULL_POINTER)
+ } else {
+ Ok(&*ptr)
+ }
+ }
+}
+
+impl<'a, T: 'a> Ensure<*const T> for Result<Option<&'a T>, nsresult> {
+ unsafe fn ensure(ptr: *const T) -> Result<Option<&'a T>, nsresult> {
+ Ok(if ptr.is_null() { None } else { Some(&*ptr) })
+ }
+}
+
+impl<T: Copy> Ensure<T> for Result<T, nsresult> {
+ unsafe fn ensure(copyable: T) -> Result<T, nsresult> {
+ Ok(copyable)
+ }
+}
diff --git a/xpcom/rust/xpcom/src/promise.rs b/xpcom/rust/xpcom/src/promise.rs
new file mode 100644
index 0000000000..0fdab9b6aa
--- /dev/null
+++ b/xpcom/rust/xpcom/src/promise.rs
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ create_instance,
+ interfaces::{nsIVariant, nsIWritableVariant},
+ RefCounted,
+};
+
+use cstr::*;
+
+mod ffi {
+ use super::*;
+
+ extern "C" {
+ // These are implemented in dom/promise/Promise.cpp
+ pub fn DomPromise_AddRef(promise: *const Promise);
+ pub fn DomPromise_Release(promise: *const Promise);
+ pub fn DomPromise_RejectWithVariant(promise: *const Promise, variant: *const nsIVariant);
+ pub fn DomPromise_ResolveWithVariant(promise: *const Promise, variant: *const nsIVariant);
+ }
+}
+
+#[repr(C)]
+pub struct Promise {
+ private: [u8; 0],
+
+ /// This field is a phantomdata to ensure that the Promise type and any
+ /// struct containing it is not safe to send across threads, as DOM is
+ /// generally not threadsafe.
+ __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>,
+}
+
+impl Promise {
+ pub fn reject_with_undefined(&self) {
+ let variant = create_instance::<nsIWritableVariant>(cstr!("@mozilla.org/variant;1"))
+ .expect("Failed to create writable variant");
+ unsafe {
+ variant.SetAsVoid();
+ }
+ self.reject_with_variant(&variant);
+ }
+
+ pub fn reject_with_variant(&self, variant: &nsIVariant) {
+ unsafe { ffi::DomPromise_RejectWithVariant(self, variant) }
+ }
+
+ pub fn resolve_with_variant(&self, variant: &nsIVariant) {
+ unsafe { ffi::DomPromise_ResolveWithVariant(self, variant) }
+ }
+}
+
+unsafe impl RefCounted for Promise {
+ unsafe fn addref(&self) {
+ ffi::DomPromise_AddRef(self)
+ }
+
+ unsafe fn release(&self) {
+ ffi::DomPromise_Release(self)
+ }
+}
diff --git a/xpcom/rust/xpcom/src/reexports.rs b/xpcom/rust/xpcom/src/reexports.rs
new file mode 100644
index 0000000000..d198899497
--- /dev/null
+++ b/xpcom/rust/xpcom/src/reexports.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The automatically generated code from `xpcom_macros` depends on some types
+//! which are defined in other libraries which `xpcom` depends on, but which may
+//! not be `extern crate`-ed into the crate the macros are expanded into. This
+//! module re-exports those types from `xpcom` so that they can be used from the
+//! macro.
+// re-export libc so it can be used by the procedural macro.
+pub extern crate libc;
+
+pub use nsstring::{nsACString, nsAString, nsCString, nsString};
+
+pub use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_OK};
+
+pub use std::ops::Deref;
+
+/// Helper method used by the xpcom codegen, it is not public API or meant for
+/// calling outside of that context.
+///
+/// Takes a reference to the `this` pointer received from XPIDL, and offsets and
+/// casts it to a reference to the concrete rust `struct` type, `U`.
+///
+/// `vtable_index` is the index, and therefore the offset in pointers, of the
+/// vtable for `T` in `U`.
+///
+/// A reference to `this` is taken, instead of taking `*const T` by value, to use
+/// as a lifetime bound, such that the returned `&U` reference has a bounded
+/// lifetime when used to call the implementation method.
+#[inline]
+pub unsafe fn transmute_from_vtable_ptr<'a, T, U>(
+ this: &'a *const T,
+ vtable_index: usize,
+) -> &'a U {
+ &*((*this as *const *const ()).sub(vtable_index) as *const U)
+}
+
+/// On some ABIs, extra information is included before the vtable's function
+/// pointers which are used to implement RTTI. We build Gecko with RTTI
+/// disabled, however these fields may still be present to support
+/// `dynamic_cast<void*>` on our rust VTables in case they are accessed.
+///
+/// Itanium ABI Layout: https://refspecs.linuxbase.org/cxxabi-1.83.html#vtable
+#[repr(C)]
+pub struct VTableExtra<T> {
+ #[cfg(not(windows))]
+ pub offset: isize,
+ #[cfg(not(windows))]
+ pub typeinfo: *const libc::c_void,
+ pub vtable: T,
+}
diff --git a/xpcom/rust/xpcom/src/refptr.rs b/xpcom/rust/xpcom/src/refptr.rs
new file mode 100644
index 0000000000..8549b6d2f0
--- /dev/null
+++ b/xpcom/rust/xpcom/src/refptr.rs
@@ -0,0 +1,388 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::interfaces::nsISupports;
+use libc;
+use nserror::{nsresult, NS_OK};
+use std::cell::Cell;
+use std::convert::TryInto;
+use std::fmt;
+use std::marker::PhantomData;
+use std::mem;
+use std::ops::Deref;
+use std::ptr::{self, NonNull};
+use std::sync::atomic::{self, AtomicUsize, Ordering};
+use threadbound::ThreadBound;
+
+// This should match the definition in mfbt/RefCountType.h, modulo the delicate
+// effort at maintaining binary compatibility with Microsoft COM on Windows.
+pub type MozExternalRefCountType = u32;
+
+/// A trait representing a type which can be reference counted invasively.
+/// The object is responsible for freeing its backing memory when its
+/// reference count reaches 0.
+pub unsafe trait RefCounted {
+ /// Increment the reference count.
+ unsafe fn addref(&self);
+ /// Decrement the reference count, potentially freeing backing memory.
+ unsafe fn release(&self);
+}
+
+/// A smart pointer holding a RefCounted object. The object itself manages its
+/// own memory. RefPtr will invoke the addref and release methods at the
+/// appropriate times to facilitate the bookkeeping.
+#[repr(transparent)]
+pub struct RefPtr<T: RefCounted + 'static> {
+ _ptr: NonNull<T>,
+ // Tell dropck that we own an instance of T.
+ _marker: PhantomData<T>,
+}
+
+impl<T: RefCounted + 'static> RefPtr<T> {
+ /// Construct a new RefPtr from a reference to the refcounted object.
+ #[inline]
+ pub fn new(p: &T) -> RefPtr<T> {
+ unsafe {
+ p.addref();
+ }
+ RefPtr {
+ _ptr: p.into(),
+ _marker: PhantomData,
+ }
+ }
+
+ /// Construct a RefPtr from a raw pointer, addrefing it.
+ #[inline]
+ pub unsafe fn from_raw(p: *const T) -> Option<RefPtr<T>> {
+ let ptr = NonNull::new(p as *mut T)?;
+ ptr.as_ref().addref();
+ Some(RefPtr {
+ _ptr: ptr,
+ _marker: PhantomData,
+ })
+ }
+
+ /// Construct a RefPtr from a raw pointer, without addrefing it.
+ #[inline]
+ pub unsafe fn from_raw_dont_addref(p: *const T) -> Option<RefPtr<T>> {
+ Some(RefPtr {
+ _ptr: NonNull::new(p as *mut T)?,
+ _marker: PhantomData,
+ })
+ }
+
+ /// Write this RefPtr's value into an outparameter.
+ #[inline]
+ pub fn forget(self, into: &mut *const T) {
+ *into = Self::forget_into_raw(self);
+ }
+
+ #[inline]
+ pub fn forget_into_raw(this: RefPtr<T>) -> *const T {
+ let into = &*this as *const T;
+ mem::forget(this);
+ into
+ }
+}
+
+impl<T: RefCounted + 'static> Deref for RefPtr<T> {
+ type Target = T;
+ #[inline]
+ fn deref(&self) -> &T {
+ unsafe { self._ptr.as_ref() }
+ }
+}
+
+impl<T: RefCounted + 'static> Drop for RefPtr<T> {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ self._ptr.as_ref().release();
+ }
+ }
+}
+
+impl<T: RefCounted + 'static> Clone for RefPtr<T> {
+ #[inline]
+ fn clone(&self) -> RefPtr<T> {
+ RefPtr::new(self)
+ }
+}
+
+impl<T: RefCounted + 'static + fmt::Debug> fmt::Debug for RefPtr<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "RefPtr<{:?}>", self.deref())
+ }
+}
+
+// Both `Send` and `Sync` bounds are required for `RefPtr<T>` to implement
+// either, as sharing a `RefPtr<T>` also allows transferring ownership, and
+// vice-versa.
+unsafe impl<T: RefCounted + 'static + Send + Sync> Send for RefPtr<T> {}
+unsafe impl<T: RefCounted + 'static + Send + Sync> Sync for RefPtr<T> {}
+
+macro_rules! assert_layout_eq {
+ ($T:ty, $U:ty) => {
+ const _: [(); std::mem::size_of::<$T>()] = [(); std::mem::size_of::<$U>()];
+ const _: [(); std::mem::align_of::<$T>()] = [(); std::mem::align_of::<$U>()];
+ };
+}
+
+// Assert that `RefPtr<nsISupports>` has the correct memory layout.
+assert_layout_eq!(RefPtr<nsISupports>, *const nsISupports);
+// Assert that the null-pointer optimization applies to `RefPtr<nsISupports>`.
+assert_layout_eq!(RefPtr<nsISupports>, Option<RefPtr<nsISupports>>);
+
+/// A wrapper that binds a RefCounted value to its original thread,
+/// preventing retrieval from other threads and panicking if the value
+/// is dropped on a different thread.
+///
+/// These limitations enable values of this type to be Send + Sync, which is
+/// useful when creating a struct that holds a RefPtr<T> type while being
+/// Send + Sync. Such a struct can hold a ThreadBoundRefPtr<T> type instead.
+pub struct ThreadBoundRefPtr<T: RefCounted + 'static>(ThreadBound<*const T>);
+
+impl<T: RefCounted + 'static> ThreadBoundRefPtr<T> {
+ pub fn new(ptr: RefPtr<T>) -> Self {
+ let raw: *const T = &*ptr;
+ mem::forget(ptr);
+ ThreadBoundRefPtr(ThreadBound::new(raw))
+ }
+
+ pub fn get_ref(&self) -> Option<&T> {
+ self.0.get_ref().map(|raw| unsafe { &**raw })
+ }
+}
+
+impl<T: RefCounted + 'static> Drop for ThreadBoundRefPtr<T> {
+ fn drop(&mut self) {
+ unsafe {
+ RefPtr::from_raw_dont_addref(self.get_ref().expect("drop() called on wrong thread!"));
+ }
+ }
+}
+
+/// A helper struct for constructing `RefPtr<T>` from raw pointer outparameters.
+/// Holds a `*const T` internally which will be released if non null when
+/// destructed, and can be easily transformed into an `Option<RefPtr<T>>`.
+///
+/// It many cases it may be easier to use the `getter_addrefs` method.
+pub struct GetterAddrefs<T: RefCounted + 'static> {
+ _ptr: *const T,
+ _marker: PhantomData<T>,
+}
+
+impl<T: RefCounted + 'static> GetterAddrefs<T> {
+ /// Create a `GetterAddrefs`, initializing it with the null pointer.
+ #[inline]
+ pub fn new() -> GetterAddrefs<T> {
+ GetterAddrefs {
+ _ptr: ptr::null(),
+ _marker: PhantomData,
+ }
+ }
+
+ /// Get a reference to the internal `*const T`. This method is unsafe,
+ /// as the destructor of this class depends on the internal `*const T`
+ /// being either a valid reference to a value of type `T`, or null.
+ #[inline]
+ pub unsafe fn ptr(&mut self) -> &mut *const T {
+ &mut self._ptr
+ }
+
+ /// Get a reference to the internal `*const T` as a `*mut libc::c_void`.
+ /// This is useful to pass to functions like `GetInterface` which take a
+ /// void pointer outparameter.
+ #[inline]
+ pub unsafe fn void_ptr(&mut self) -> *mut *mut libc::c_void {
+ &mut self._ptr as *mut *const T as *mut *mut libc::c_void
+ }
+
+ /// Transform this `GetterAddrefs` into an `Option<RefPtr<T>>`, without
+ /// performing any addrefs or releases.
+ #[inline]
+ pub fn refptr(self) -> Option<RefPtr<T>> {
+ let p = self._ptr;
+ // Don't run the destructor because we don't want to release the stored
+ // pointer.
+ mem::forget(self);
+ unsafe { RefPtr::from_raw_dont_addref(p) }
+ }
+}
+
+impl<T: RefCounted + 'static> Drop for GetterAddrefs<T> {
+ #[inline]
+ fn drop(&mut self) {
+ if !self._ptr.is_null() {
+ unsafe {
+ (*self._ptr).release();
+ }
+ }
+ }
+}
+
+/// Helper method for calling XPCOM methods which return a reference counted
+/// value through an outparameter. Takes a lambda, which is called with a valid
+/// outparameter argument (`*mut *const T`), and returns a `nsresult`. Returns
+/// either a `RefPtr<T>` with the value returned from the outparameter, or a
+/// `nsresult`.
+///
+/// # NOTE:
+///
+/// Can return `Err(NS_OK)` if the call succeeded, but the outparameter was set
+/// to NULL.
+///
+/// # Usage
+///
+/// ```
+/// let x: Result<RefPtr<T>, nsresult> =
+/// getter_addrefs(|p| iosvc.NewURI(uri, ptr::null(), ptr::null(), p));
+/// ```
+#[inline]
+pub fn getter_addrefs<T: RefCounted, F>(f: F) -> Result<RefPtr<T>, nsresult>
+where
+ F: FnOnce(*mut *const T) -> nsresult,
+{
+ let mut ga = GetterAddrefs::<T>::new();
+ let rv = f(unsafe { ga.ptr() });
+ if rv.failed() {
+ return Err(rv);
+ }
+ ga.refptr().ok_or(NS_OK)
+}
+
+/// The type of the reference count type for xpcom structs.
+///
+/// `#[xpcom(nonatomic)]` will use this type for the `__refcnt` field.
+#[derive(Debug)]
+pub struct Refcnt(Cell<usize>);
+impl Refcnt {
+ /// Create a new reference count value. This is unsafe as manipulating
+ /// Refcnt values is an easy footgun.
+ pub unsafe fn new() -> Self {
+ Refcnt(Cell::new(0))
+ }
+
+ /// Increment the reference count. Returns the new reference count. This is
+ /// unsafe as modifying this value can cause a use-after-free.
+ pub unsafe fn inc(&self) -> MozExternalRefCountType {
+ // XXX: Checked add?
+ let new = self.0.get() + 1;
+ self.0.set(new);
+ new.try_into().unwrap()
+ }
+
+ /// Decrement the reference count. Returns the new reference count. This is
+ /// unsafe as modifying this value can cause a use-after-free.
+ pub unsafe fn dec(&self) -> MozExternalRefCountType {
+ // XXX: Checked sub?
+ let new = self.0.get() - 1;
+ self.0.set(new);
+ new.try_into().unwrap()
+ }
+
+ /// Get the current value of the reference count.
+ pub fn get(&self) -> usize {
+ self.0.get()
+ }
+}
+
+/// The type of the atomic reference count used for xpcom structs.
+///
+/// `#[xpcom(atomic)]` will use this type for the `__refcnt` field.
+///
+/// See `nsISupportsImpl.h`'s `ThreadSafeAutoRefCnt` class for reasoning behind
+/// memory ordering decisions.
+#[derive(Debug)]
+pub struct AtomicRefcnt(AtomicUsize);
+impl AtomicRefcnt {
+ /// Create a new reference count value. This is unsafe as manipulating
+ /// Refcnt values is an easy footgun.
+ pub unsafe fn new() -> Self {
+ AtomicRefcnt(AtomicUsize::new(0))
+ }
+
+ /// Increment the reference count. Returns the new reference count. This is
+ /// unsafe as modifying this value can cause a use-after-free.
+ pub unsafe fn inc(&self) -> MozExternalRefCountType {
+ let result = self.0.fetch_add(1, Ordering::Relaxed) + 1;
+ result.try_into().unwrap()
+ }
+
+ /// Decrement the reference count. Returns the new reference count. This is
+ /// unsafe as modifying this value can cause a use-after-free.
+ pub unsafe fn dec(&self) -> MozExternalRefCountType {
+ let result = self.0.fetch_sub(1, Ordering::Release) - 1;
+ if result == 0 {
+ // We're going to destroy the object on this thread, so we need
+ // acquire semantics to synchronize with the memory released by
+ // the last release on other threads, that is, to ensure that
+ // writes prior to that release are now visible on this thread.
+ if cfg!(feature = "thread_sanitizer") {
+ // TSan doesn't understand atomic::fence, so in order to avoid
+ // a false positive for every time a refcounted object is
+ // deleted, we replace the fence with an atomic operation.
+ self.0.load(Ordering::Acquire);
+ } else {
+ atomic::fence(Ordering::Acquire);
+ }
+ }
+ result.try_into().unwrap()
+ }
+
+ /// Get the current value of the reference count.
+ pub fn get(&self) -> usize {
+ self.0.load(Ordering::Acquire)
+ }
+}
+
+#[cfg(feature = "gecko_refcount_logging")]
+pub mod trace_refcnt {
+ extern "C" {
+ pub fn NS_LogCtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32);
+ pub fn NS_LogDtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32);
+ pub fn NS_LogAddRef(
+ aPtr: *mut libc::c_void,
+ aRefcnt: usize,
+ aClass: *const libc::c_char,
+ aClassSize: u32,
+ );
+ pub fn NS_LogRelease(
+ aPtr: *mut libc::c_void,
+ aRefcnt: usize,
+ aClass: *const libc::c_char,
+ aClassSize: u32,
+ );
+ }
+}
+
+// stub inline methods for the refcount logging functions for when the feature
+// is disabled.
+#[cfg(not(feature = "gecko_refcount_logging"))]
+pub mod trace_refcnt {
+ #[inline]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn NS_LogCtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {}
+ #[inline]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn NS_LogDtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {}
+ #[inline]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn NS_LogAddRef(
+ _: *mut libc::c_void,
+ _: usize,
+ _: *const libc::c_char,
+ _: u32,
+ ) {
+ }
+ #[inline]
+ #[allow(non_snake_case)]
+ pub unsafe extern "C" fn NS_LogRelease(
+ _: *mut libc::c_void,
+ _: usize,
+ _: *const libc::c_char,
+ _: u32,
+ ) {
+ }
+}
diff --git a/xpcom/rust/xpcom/src/statics.rs b/xpcom/rust/xpcom/src/statics.rs
new file mode 100644
index 0000000000..27f66fdb4b
--- /dev/null
+++ b/xpcom/rust/xpcom/src/statics.rs
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::interfaces::{nsIComponentManager, nsIComponentRegistrar, nsIServiceManager};
+use crate::{GetterAddrefs, RefPtr, XpCom};
+use std::ffi::CStr;
+
+/// Get a reference to the global `nsIComponentManager`.
+///
+/// Can return `None` during shutdown.
+#[inline]
+pub fn component_manager() -> Option<RefPtr<nsIComponentManager>> {
+ unsafe { RefPtr::from_raw(Gecko_GetComponentManager()) }
+}
+
+/// Get a reference to the global `nsIServiceManager`.
+///
+/// Can return `None` during shutdown.
+#[inline]
+pub fn service_manager() -> Option<RefPtr<nsIServiceManager>> {
+ unsafe { RefPtr::from_raw(Gecko_GetServiceManager()) }
+}
+
+/// Get a reference to the global `nsIComponentRegistrar`
+///
+/// Can return `None` during shutdown.
+#[inline]
+pub fn component_registrar() -> Option<RefPtr<nsIComponentRegistrar>> {
+ unsafe { RefPtr::from_raw(Gecko_GetComponentRegistrar()) }
+}
+
+/// Helper for calling `nsIComponentManager::CreateInstanceByContractID` on the
+/// global `nsIComponentRegistrar`.
+///
+/// This method is similar to `do_CreateInstance` in C++.
+#[inline]
+pub fn create_instance<T: XpCom>(id: &CStr) -> Option<RefPtr<T>> {
+ unsafe {
+ let mut ga = GetterAddrefs::<T>::new();
+ if component_manager()?
+ .CreateInstanceByContractID(id.as_ptr(), &T::IID, ga.void_ptr())
+ .succeeded()
+ {
+ ga.refptr()
+ } else {
+ None
+ }
+ }
+}
+
+/// Helper for calling `nsIServiceManager::GetServiceByContractID` on the global
+/// `nsIServiceManager`.
+///
+/// This method is similar to `do_GetService` in C++.
+#[inline]
+pub fn get_service<T: XpCom>(id: &CStr) -> Option<RefPtr<T>> {
+ unsafe {
+ let mut ga = GetterAddrefs::<T>::new();
+ if service_manager()?
+ .GetServiceByContractID(id.as_ptr(), &T::IID, ga.void_ptr())
+ .succeeded()
+ {
+ ga.refptr()
+ } else {
+ None
+ }
+ }
+}
+
+extern "C" {
+ fn Gecko_GetComponentManager() -> *const nsIComponentManager;
+ fn Gecko_GetServiceManager() -> *const nsIServiceManager;
+ fn Gecko_GetComponentRegistrar() -> *const nsIComponentRegistrar;
+}
diff --git a/xpcom/rust/xpcom/xpcom_macros/Cargo.toml b/xpcom/rust/xpcom/xpcom_macros/Cargo.toml
new file mode 100644
index 0000000000..4b5bcdc315
--- /dev/null
+++ b/xpcom/rust/xpcom/xpcom_macros/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "xpcom_macros"
+version = "0.1.0"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = "1"
+quote = "1"
+proc-macro2 = "1"
+lazy_static = "1.0"
+mozbuild = "0.1"
diff --git a/xpcom/rust/xpcom/xpcom_macros/src/lib.rs b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs
new file mode 100644
index 0000000000..2511ba9103
--- /dev/null
+++ b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs
@@ -0,0 +1,818 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This crate provides the `#[xpcom]` custom attribute. This custom attribute
+//! is used in order to implement [`xpcom`] interfaces.
+//!
+//! # Usage
+//!
+//! The easiest way to explain this crate is probably with a usage example. I'll
+//! show you the example, and then we'll destructure it and walk through what
+//! each component is doing.
+//!
+//! ```ignore
+//! // Declaring an XPCOM Struct
+//! #[xpcom(implement(nsIRunnable), atomic)]
+//! struct ImplRunnable {
+//! i: i32,
+//! }
+//!
+//! // Implementing methods on an XPCOM Struct
+//! impl ImplRunnable {
+//! unsafe fn Run(&self) -> nsresult {
+//! println!("{}", self.i);
+//! NS_OK
+//! }
+//! }
+//! ```
+//!
+//! ## Declaring an XPCOM Struct
+//!
+//! ```ignore
+//! // This derive should be placed on the initialization struct in order to
+//! // trigger the procedural macro.
+//! #[xpcom(
+//! // The implement argument should be passed the names of the IDL
+//! // interfaces which you want to implement. These can be separated by
+//! // commas if you want to implement multiple interfaces.
+//! //
+//! // Some methods use types which we cannot bind to in rust. Interfaces
+//! // like those cannot be implemented, and a compile-time error will occur
+//! // if they are listed in this attribute.
+//! implement(nsIRunnable),
+//!
+//! // The refcount kind can be specified as one of the following values:
+//! // * `atomic` == atomic reference count
+//! // ~= NS_DECL_THREADSAFE_ISUPPORTS in C++
+//! // * `nonatomic` == non atomic reference count
+//! // ~= NS_DECL_ISUPPORTS in C++
+//! atomic,
+//! )]
+//!
+//! // It is a compile time error to put the `#[xpcom]` attribute on
+//! // an enum, union, or tuple struct.
+//! //
+//! // The macro will generate both the named struct, as well as a version with
+//! // its name prefixed with `Init` which can be used to initialize the type.
+//! struct ImplRunnable {
+//! i: i32,
+//! }
+//! ```
+//!
+//! The above example will generate `ImplRunnable` and `InitImplRunnable`
+//! structs. The `ImplRunnable` struct will implement the [`nsIRunnable`] XPCOM
+//! interface, and cannot be constructed directly.
+//!
+//! The following methods will be automatically implemented on it:
+//!
+//! ```ignore
+//! // Automatic nsISupports implementation
+//! unsafe fn AddRef(&self) -> MozExternalRefCountType;
+//! unsafe fn Release(&self) -> MozExternalRefCountType;
+//! unsafe fn QueryInterface(&self, uuid: &nsIID, result: *mut *mut libc::c_void) -> nsresult;
+//!
+//! // Allocates and initializes a new instance of this type. The values will
+//! // be moved from the `Init` struct which is passed in.
+//! fn allocate(init: InitImplRunnable) -> RefPtr<Self>;
+//!
+//! // Helper for performing the `query_interface` operation to case to a
+//! // specific interface.
+//! fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>>;
+//!
+//! // Coerce function for cheaply casting to our base interfaces.
+//! fn coerce<T: ImplRunnableCoerce>(&self) -> &T;
+//! ```
+//!
+//! The [`RefCounted`] interface will also be implemented, so that the type can
+//! be used within the [`RefPtr`] type.
+//!
+//! The `coerce` and `query_interface` methods are backed by the generated
+//! `*Coerce` trait. This trait is impl-ed for every interface implemented by
+//! the trait. For example:
+//!
+//! ```ignore
+//! pub trait ImplRunnableCoerce {
+//! fn coerce_from(x: &ImplRunnable) -> &Self;
+//! }
+//! impl ImplRunnableCoerce for nsIRunnable { .. }
+//! impl ImplRunnableCoerce for nsISupports { .. }
+//! ```
+//!
+//! ## Implementing methods on an XPCOM Struct
+//!
+//! ```ignore
+//! // Methods should be implemented directly on the generated struct. All
+//! // methods other than `AddRef`, `Release`, and `QueryInterface` must be
+//! // implemented manually.
+//! impl ImplRunnable {
+//! // The method should have the same name as the corresponding C++ method.
+//! unsafe fn Run(&self) -> nsresult {
+//! // Fields defined on the template struct will be directly on the
+//! // generated struct.
+//! println!("{}", self.i);
+//! NS_OK
+//! }
+//! }
+//! ```
+//!
+//! XPCOM methods implemented in Rust have signatures similar to methods
+//! implemented in C++.
+//!
+//! ```ignore
+//! // nsISupports foo(in long long bar, in AString baz);
+//! unsafe fn Foo(&self, bar: i64, baz: *const nsAString,
+//! _retval: *mut *const nsISupports) -> nsresult;
+//!
+//! // AString qux(in nsISupports ham);
+//! unsafe fn Qux(&self, ham: *const nsISupports,
+//! _retval: *mut nsAString) -> nsresult;
+//! ```
+//!
+//! This is a little tedious, so the `xpcom_method!` macro provides a convenient
+//! way to generate wrappers around more idiomatic Rust methods.
+//!
+//! [`xpcom`]: ../xpcom/index.html
+//! [`nsIRunnable`]: ../xpcom/struct.nsIRunnable.html
+//! [`RefCounted`]: ../xpcom/struct.RefCounted.html
+//! [`RefPtr`]: ../xpcom/struct.RefPtr.html
+
+use lazy_static::lazy_static;
+use proc_macro2::{Span, TokenStream};
+use quote::{format_ident, quote, ToTokens};
+use std::collections::{HashMap, HashSet};
+use syn::punctuated::Punctuated;
+use syn::{
+ parse_macro_input, parse_quote, AttributeArgs, Field, Fields, Ident, ItemStruct, Meta,
+ MetaList, NestedMeta, Path, Token, Type,
+};
+
+macro_rules! bail {
+ (@($t:expr), $s:expr) => {
+ return Err(syn::Error::new_spanned(&$t, &$s[..]))
+ };
+ (@($t:expr), $f:expr, $($e:expr),*) => {
+ return Err(syn::Error::new_spanned(&$t, &format!($f, $($e),*)[..]))
+ };
+ ($s:expr) => {
+ return Err(syn::Error::new(Span::call_site(), &$s[..]))
+ };
+ ($f:expr, $($e:expr),*) => {
+ return Err(syn::Error::new(Span::call_site(), &format!($f, $($e),*)[..]))
+ };
+}
+
+/* These are the structs generated by the rust_macros.py script */
+
+/// A single parameter to an XPCOM method.
+#[derive(Debug)]
+struct Param {
+ name: &'static str,
+ ty: &'static str,
+}
+
+/// A single method on an XPCOM interface.
+#[derive(Debug)]
+struct Method {
+ name: &'static str,
+ params: &'static [Param],
+ ret: &'static str,
+}
+
+/// An XPCOM interface. `methods` will be `Err("reason")` if the interface
+/// cannot be implemented in rust code.
+#[derive(Debug)]
+struct Interface {
+ name: &'static str,
+ base: Option<&'static str>,
+ methods: Result<&'static [Method], &'static str>,
+}
+
+impl Interface {
+ fn base(&self) -> Option<&'static Interface> {
+ Some(IFACES[self.base?])
+ }
+
+ fn methods(&self) -> Result<&'static [Method], syn::Error> {
+ match self.methods {
+ Ok(methods) => Ok(methods),
+ Err(reason) => Err(syn::Error::new(
+ Span::call_site(),
+ &format!(
+ "Interface {} cannot be implemented in rust \
+ because {} is not supported yet",
+ self.name, reason
+ ),
+ )),
+ }
+ }
+}
+
+lazy_static! {
+ /// This item contains the information generated by the procedural macro in
+ /// the form of a `HashMap` from interface names to their descriptions.
+ static ref IFACES: HashMap<&'static str, &'static Interface> = {
+ let lists: &[&[Interface]] =
+ include!(mozbuild::objdir_path!("dist/xpcrs/bt/all.rs"));
+
+ let mut hm = HashMap::new();
+ for &list in lists {
+ for iface in list {
+ hm.insert(iface.name, iface);
+ }
+ }
+ hm
+ };
+}
+
+/// The type of the reference count to use for the struct.
+#[derive(Debug, Eq, PartialEq, Copy, Clone)]
+enum RefcntKind {
+ Atomic,
+ NonAtomic,
+}
+
+/// Produces the tokens for the type representation.
+impl ToTokens for RefcntKind {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match *self {
+ RefcntKind::NonAtomic => quote!(xpcom::Refcnt).to_tokens(tokens),
+ RefcntKind::Atomic => quote!(xpcom::AtomicRefcnt).to_tokens(tokens),
+ }
+ }
+}
+
+/// Extract the fields list from the input struct.
+fn get_fields(si: &ItemStruct) -> Result<&Punctuated<Field, Token![,]>, syn::Error> {
+ match si.fields {
+ Fields::Named(ref named) => Ok(&named.named),
+ _ => bail!(@(si), "The initializer struct must be a standard named \
+ value struct definition"),
+ }
+}
+
+/// Takes the template struct in, and generates `ItemStruct` for the "real" and
+/// "init" structs.
+fn gen_structs(
+ template: &ItemStruct,
+ bases: &[&Interface],
+ refcnt_ty: RefcntKind,
+) -> Result<(ItemStruct, ItemStruct), syn::Error> {
+ let real_ident = &template.ident;
+ let init_ident = format_ident!("Init{}", real_ident);
+ let vis = &template.vis;
+
+ let bases = bases.iter().map(|base| {
+ let ident = format_ident!("__base_{}", base.name);
+ let vtable = format_ident!("{}VTable", base.name);
+ quote!(#ident : *const xpcom::interfaces::#vtable)
+ });
+
+ let fields = get_fields(template)?;
+ let (impl_generics, _, where_clause) = template.generics.split_for_impl();
+ Ok((
+ parse_quote! {
+ #[repr(C)]
+ #vis struct #real_ident #impl_generics #where_clause {
+ #(#bases,)*
+ __refcnt: #refcnt_ty,
+ #fields
+ }
+ },
+ parse_quote! {
+ #vis struct #init_ident #impl_generics #where_clause {
+ #fields
+ }
+ },
+ ))
+}
+
+/// Generates the `extern "system"` methods which are actually included in the
+/// VTable for the given interface.
+///
+/// `idx` must be the offset in pointers of the pointer to this vtable in the
+/// struct `real`. This is soundness-critical, as it will be used to offset
+/// pointers received from xpcom back to the concrete implementation.
+fn gen_vtable_methods(
+ real: &ItemStruct,
+ iface: &Interface,
+ vtable_index: usize,
+) -> Result<TokenStream, syn::Error> {
+ let base_ty = format_ident!("{}", iface.name);
+
+ let base_methods = if let Some(base) = iface.base() {
+ gen_vtable_methods(real, base, vtable_index)?
+ } else {
+ quote! {}
+ };
+
+ let ty_name = &real.ident;
+ let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl();
+
+ let mut method_defs = Vec::new();
+ for method in iface.methods()? {
+ let ret = syn::parse_str::<Type>(method.ret)?;
+
+ let mut params = Vec::new();
+ let mut args = Vec::new();
+ for param in method.params {
+ let name = format_ident!("{}", param.name);
+ let ty = syn::parse_str::<Type>(param.ty)?;
+
+ params.push(quote! {#name : #ty,});
+ args.push(quote! {#name,});
+ }
+
+ let name = format_ident!("{}", method.name);
+ method_defs.push(quote! {
+ unsafe extern "system" fn #name #impl_generics (
+ this: *const #base_ty, #(#params)*
+ ) -> #ret #where_clause {
+ let this: &#ty_name #ty_generics =
+ ::xpcom::reexports::transmute_from_vtable_ptr(&this, #vtable_index);
+ this.#name(#(#args)*)
+ }
+ });
+ }
+
+ Ok(quote! {
+ #base_methods
+ #(#method_defs)*
+ })
+}
+
+/// Generates the VTable for a given base interface. This assumes that the
+/// implementations of each of the `extern "system"` methods are in scope.
+fn gen_inner_vtable(real: &ItemStruct, iface: &Interface) -> Result<TokenStream, syn::Error> {
+ let vtable_ty = format_ident!("{}VTable", iface.name);
+
+ // Generate the vtable for the base interface.
+ let base_vtable = if let Some(base) = iface.base() {
+ let vt = gen_inner_vtable(real, base)?;
+ quote! {__base: #vt,}
+ } else {
+ quote! {}
+ };
+
+ // Include each of the method definitions for this interface.
+ let (_, ty_generics, _) = real.generics.split_for_impl();
+ let turbofish = ty_generics.as_turbofish();
+ let vtable_init = iface
+ .methods()?
+ .into_iter()
+ .map(|method| {
+ let name = format_ident!("{}", method.name);
+ quote! { #name : #name #turbofish, }
+ })
+ .collect::<Vec<_>>();
+
+ Ok(quote!(#vtable_ty {
+ #base_vtable
+ #(#vtable_init)*
+ }))
+}
+
+fn gen_root_vtable(
+ real: &ItemStruct,
+ base: &Interface,
+ idx: usize,
+) -> Result<TokenStream, syn::Error> {
+ let field = format_ident!("__base_{}", base.name);
+ let vtable_ty = format_ident!("{}VTable", base.name);
+
+ let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl();
+ let turbofish = ty_generics.as_turbofish();
+
+ let methods = gen_vtable_methods(real, base, idx)?;
+ let vtable = gen_inner_vtable(real, base)?;
+
+ // Define the `recover_self` method. This performs an offset calculation to
+ // recover a pointer to the original struct from a pointer to the given
+ // VTable field.
+ Ok(quote! {#field: {
+ // The method implementations which will be used to build the vtable.
+ #methods
+
+ // The actual VTable definition. This is in a separate method in order
+ // to allow it to be generic.
+ #[inline]
+ fn get_vtable #impl_generics () -> &'static ::xpcom::reexports::VTableExtra<#vtable_ty> #where_clause {
+ &::xpcom::reexports::VTableExtra {
+ #[cfg(not(windows))]
+ offset: {
+ // NOTE: workaround required to avoid depending on the
+ // unstable const expression feature `const {}`.
+ const OFFSET: isize = -((::std::mem::size_of::<usize>() * #idx) as isize);
+ OFFSET
+ },
+ #[cfg(not(windows))]
+ typeinfo: 0 as *const _,
+ vtable: #vtable,
+ }
+ }
+ &get_vtable #turbofish ().vtable
+ },})
+}
+
+/// Generate the cast implementations. This generates the implementation details
+/// for the `Coerce` trait, and the `QueryInterface` method. The first return
+/// value is the `QueryInterface` implementation, and the second is the `Coerce`
+/// implementation.
+fn gen_casts(
+ seen: &mut HashSet<&'static str>,
+ iface: &Interface,
+ real: &ItemStruct,
+ coerce_name: &Ident,
+ vtable_field: &Ident,
+) -> Result<(TokenStream, TokenStream), syn::Error> {
+ if !seen.insert(iface.name) {
+ return Ok((quote! {}, quote! {}));
+ }
+
+ // Generate the cast implementations for the base interfaces.
+ let (base_qi, base_coerce) = if let Some(base) = iface.base() {
+ gen_casts(seen, base, real, coerce_name, vtable_field)?
+ } else {
+ (quote! {}, quote! {})
+ };
+
+ // Add the if statment to QueryInterface for the base class.
+ let base_name = format_ident!("{}", iface.name);
+
+ let qi = quote! {
+ #base_qi
+ if *uuid == #base_name::IID {
+ // Implement QueryInterface in terms of coercions.
+ self.addref();
+ *result = self.coerce::<#base_name>()
+ as *const #base_name
+ as *const ::xpcom::reexports::libc::c_void
+ as *mut ::xpcom::reexports::libc::c_void;
+ return ::xpcom::reexports::NS_OK;
+ }
+ };
+
+ // Add an implementation of the `*Coerce` trait for the base interface.
+ let name = &real.ident;
+ let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl();
+ let coerce = quote! {
+ #base_coerce
+
+ impl #impl_generics #coerce_name #ty_generics for ::xpcom::interfaces::#base_name #where_clause {
+ fn coerce_from(v: &#name #ty_generics) -> &Self {
+ unsafe {
+ // Get the address of the VTable field. This should be a
+ // pointer to a pointer to a vtable, which we can then cast
+ // into a pointer to our interface.
+ &*(&(v.#vtable_field)
+ as *const *const _
+ as *const ::xpcom::interfaces::#base_name)
+ }
+ }
+ }
+ };
+
+ Ok((qi, coerce))
+}
+
+fn check_generics(generics: &syn::Generics) -> Result<(), syn::Error> {
+ for param in &generics.params {
+ let tp = match param {
+ syn::GenericParam::Type(tp) => tp,
+ syn::GenericParam::Lifetime(lp) => bail!(
+ @(lp),
+ "Cannot use #[xpcom] on types with lifetime parameters. \
+ Implementors of XPCOM interfaces must not contain non-'static \
+ lifetimes.",
+ ),
+ // XXX: Once const generics become stable, it may be as simple as
+ // removing this bail! to support them.
+ syn::GenericParam::Const(cp) => {
+ bail!(@(cp), "Cannot use #[xpcom] on types with const generics.")
+ }
+ };
+
+ let mut static_lt = false;
+ for bound in &tp.bounds {
+ match bound {
+ syn::TypeParamBound::Lifetime(lt) if lt.ident == "static" => {
+ static_lt = true;
+ break;
+ }
+ _ => {}
+ }
+ }
+
+ if !static_lt {
+ bail!(
+ @(param),
+ "Every generic parameter for xpcom implementation must have a \
+ 'static lifetime bound declared in the generics. Implicit \
+ lifetime bounds or lifetime bounds in where clauses are not \
+ detected by the macro and will be ignored. \
+ Implementors of XPCOM interfaces must not contain non-'static \
+ lifetimes.",
+ );
+ }
+ }
+ Ok(())
+}
+
+#[derive(Default)]
+struct Options {
+ bases: Vec<&'static Interface>,
+ refcnt: Option<RefcntKind>,
+}
+
+impl Options {
+ fn parse_path_arg(&mut self, path: &Path) -> Result<(), syn::Error> {
+ if path.is_ident("atomic") || path.is_ident("nonatomic") {
+ if self.refcnt.is_some() {
+ bail!(@(path), "Duplicate refcnt atomicity specifier");
+ }
+ self.refcnt = Some(if path.is_ident("atomic") {
+ RefcntKind::Atomic
+ } else {
+ RefcntKind::NonAtomic
+ });
+ return Ok(());
+ }
+ bail!(@(path), "Unexpected path argument to #[xpcom]");
+ }
+
+ fn parse_list_arg(&mut self, list: &MetaList) -> Result<(), syn::Error> {
+ if list.path.is_ident("implement") {
+ for item in list.nested.iter() {
+ let iface = match *item {
+ NestedMeta::Meta(syn::Meta::Path(ref iface)) => iface,
+ _ => bail!(@(item), "Expected interface name to implement"),
+ };
+ let ident = match iface.get_ident() {
+ Some(ref iface) => iface.to_string(),
+ _ => bail!(@(iface), "Interface name must be unqualified"),
+ };
+ if let Some(&iface) = IFACES.get(ident.as_str()) {
+ self.bases.push(iface);
+ } else {
+ bail!(@(item), "Invalid base interface `{}`", ident);
+ }
+ }
+ return Ok(());
+ }
+ bail!(@(list), "Unexpected list argument to #[xpcom]");
+ }
+
+ fn parse(&mut self, args: &AttributeArgs) -> Result<(), syn::Error> {
+ for arg in args {
+ match arg {
+ NestedMeta::Meta(Meta::Path(path)) => self.parse_path_arg(path)?,
+ NestedMeta::Meta(Meta::List(list)) => self.parse_list_arg(list)?,
+ NestedMeta::Meta(Meta::NameValue(name_value)) => {
+ bail!(@(name_value), "Unexpected name-value argument to #[xpcom]")
+ }
+ NestedMeta::Lit(lit) => bail!(@(lit), "Unexpected literal argument to #[xpcom]"),
+ }
+ }
+
+ if self.bases.is_empty() {
+ bail!(
+ "Types with #[xpcom(..)] must implement at least one \
+ interface. Interfaces can be implemented by adding an \
+ implements(nsIFoo, nsIBar) parameter to the #[xpcom] attribute"
+ );
+ }
+
+ if self.refcnt.is_none() {
+ bail!("Must specify refcnt kind in #[xpcom] attribute");
+ }
+
+ Ok(())
+ }
+}
+
+/// The root xpcom procedural macro definition.
+fn xpcom_impl(args: AttributeArgs, template: ItemStruct) -> Result<TokenStream, syn::Error> {
+ let mut options = Options::default();
+ options.parse(&args)?;
+
+ check_generics(&template.generics)?;
+
+ let bases = options.bases;
+
+ // Ensure that all our base interface methods have unique names.
+ let mut method_names = HashMap::new();
+ for base in &bases {
+ for method in base.methods()? {
+ if let Some(existing) = method_names.insert(method.name, base.name) {
+ bail!(
+ "The method `{0}` is declared on both `{1}` and `{2}`,
+ but a Rust type cannot implement two methods with the \
+ same name. You can add the `[binaryname(Renamed{0})]` \
+ XPIDL attribute to one of the declarations to rename it.",
+ method.name,
+ existing,
+ base.name
+ );
+ }
+ }
+ }
+
+ // Determine what reference count type to use, and generate the real struct.
+ let refcnt_ty = options.refcnt.unwrap();
+ let (real, init) = gen_structs(&template, &bases, refcnt_ty)?;
+
+ let name_init = &init.ident;
+ let name = &real.ident;
+ let coerce_name = format_ident!("{}Coerce", name);
+
+ // Generate a VTable for each of the base interfaces.
+ let mut vtables = Vec::new();
+ for (idx, base) in bases.iter().enumerate() {
+ vtables.push(gen_root_vtable(&real, base, idx)?);
+ }
+
+ // Generate the field initializers for the final struct, moving each field
+ // out of the original __init struct.
+ let inits = get_fields(&init)?.iter().map(|field| {
+ let id = &field.ident;
+ quote! { #id : __init.#id, }
+ });
+
+ let vis = &real.vis;
+
+ // Generate the implementation for QueryInterface and Coerce.
+ let mut seen = HashSet::new();
+ let mut qi_impl = Vec::new();
+ let mut coerce_impl = Vec::new();
+ for base in &bases {
+ let (qi, coerce) = gen_casts(
+ &mut seen,
+ base,
+ &real,
+ &coerce_name,
+ &format_ident!("__base_{}", base.name),
+ )?;
+ qi_impl.push(qi);
+ coerce_impl.push(coerce);
+ }
+
+ let size_for_logs = if real.generics.params.is_empty() {
+ quote!(::std::mem::size_of::<Self>() as u32)
+ } else {
+ // Refcount logging requires all types with the same name to have the
+ // same size, and generics aren't taken into account when creating our
+ // name string, so we need to make sure that all possible instantiations
+ // report the same size. To do that, we fake a size based on the number
+ // of vtable pointers and the known refcount field.
+ let fake_size_npointers = bases.len() + 1;
+ quote!((::std::mem::size_of::<usize>() * #fake_size_npointers) as u32)
+ };
+
+ let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl();
+ let name_for_logs = quote!(
+ concat!(module_path!(), "::", stringify!(#name #ty_generics), "\0").as_ptr()
+ as *const ::xpcom::reexports::libc::c_char
+ );
+ Ok(quote! {
+ #init
+
+ #real
+
+ impl #impl_generics #name #ty_generics #where_clause {
+ /// This method is used for
+ fn allocate(__init: #name_init #ty_generics) -> ::xpcom::RefPtr<Self> {
+ #[allow(unused_imports)]
+ use ::xpcom::*;
+ #[allow(unused_imports)]
+ use ::xpcom::interfaces::*;
+ #[allow(unused_imports)]
+ use ::xpcom::reexports::{
+ libc, nsACString, nsAString, nsCString, nsString, nsresult
+ };
+
+ // Helper for asserting that for all instantiations, this
+ // object has the 'static lifetime.
+ fn xpcom_types_must_be_static<T: 'static>(t: &T) {}
+
+ unsafe {
+ // NOTE: This is split into multiple lines to make the
+ // output more readable.
+ let value = #name {
+ #(#vtables)*
+ __refcnt: #refcnt_ty::new(),
+ #(#inits)*
+ };
+ let boxed = ::std::boxed::Box::new(value);
+ xpcom_types_must_be_static(&*boxed);
+ let raw = ::std::boxed::Box::into_raw(boxed);
+ ::xpcom::RefPtr::from_raw(raw).unwrap()
+ }
+ }
+
+ /// Automatically generated implementation of AddRef for nsISupports.
+ #vis unsafe fn AddRef(&self) -> ::xpcom::MozExternalRefCountType {
+ let new = self.__refcnt.inc();
+ ::xpcom::trace_refcnt::NS_LogAddRef(
+ self as *const _ as *mut ::xpcom::reexports::libc::c_void,
+ new as usize,
+ #name_for_logs,
+ #size_for_logs,
+ );
+ new
+ }
+
+ /// Automatically generated implementation of Release for nsISupports.
+ #vis unsafe fn Release(&self) -> ::xpcom::MozExternalRefCountType {
+ let new = self.__refcnt.dec();
+ ::xpcom::trace_refcnt::NS_LogRelease(
+ self as *const _ as *mut ::xpcom::reexports::libc::c_void,
+ new as usize,
+ #name_for_logs,
+ #size_for_logs,
+ );
+ if new == 0 {
+ // dealloc
+ ::std::mem::drop(::std::boxed::Box::from_raw(self as *const Self as *mut Self));
+ }
+ new
+ }
+
+ /// Automatically generated implementation of QueryInterface for
+ /// nsISupports.
+ #vis unsafe fn QueryInterface(&self,
+ uuid: *const ::xpcom::nsIID,
+ result: *mut *mut ::xpcom::reexports::libc::c_void)
+ -> ::xpcom::reexports::nsresult {
+ #[allow(unused_imports)]
+ use ::xpcom::*;
+ #[allow(unused_imports)]
+ use ::xpcom::interfaces::*;
+
+ #(#qi_impl)*
+
+ ::xpcom::reexports::NS_ERROR_NO_INTERFACE
+ }
+
+ /// Perform a QueryInterface call on this object, attempting to
+ /// dynamically cast it to the requested interface type. Returns
+ /// Some(RefPtr<T>) if the cast succeeded, and None otherwise.
+ #vis fn query_interface<XPCOM_InterfaceType: ::xpcom::XpCom>(&self)
+ -> ::std::option::Option<::xpcom::RefPtr<XPCOM_InterfaceType>>
+ {
+ let mut ga = ::xpcom::GetterAddrefs::<XPCOM_InterfaceType>::new();
+ unsafe {
+ if self.QueryInterface(&XPCOM_InterfaceType::IID, ga.void_ptr()).succeeded() {
+ ga.refptr()
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Coerce this type safely to any of the interfaces which it
+ /// implements without `AddRef`ing it.
+ #vis fn coerce<XPCOM_InterfaceType: #coerce_name #ty_generics>(&self) -> &XPCOM_InterfaceType {
+ XPCOM_InterfaceType::coerce_from(self)
+ }
+ }
+
+ /// This trait is implemented on the interface types which this
+ /// `#[xpcom]` type can be safely ane cheaply coerced to using the
+ /// `coerce` method.
+ ///
+ /// The trait and its method should usually not be used directly, but
+ /// rather acts as a trait bound and implementation for the `coerce`
+ /// methods.
+ #[doc(hidden)]
+ #vis trait #coerce_name #impl_generics #where_clause {
+ /// Convert a value of the `#[xpcom]` type into the implementing
+ /// interface type.
+ fn coerce_from(v: &#name #ty_generics) -> &Self;
+ }
+
+ #(#coerce_impl)*
+
+ unsafe impl #impl_generics ::xpcom::RefCounted for #name #ty_generics #where_clause {
+ unsafe fn addref(&self) {
+ self.AddRef();
+ }
+
+ unsafe fn release(&self) {
+ self.Release();
+ }
+ }
+ })
+}
+
+#[proc_macro_attribute]
+pub fn xpcom(
+ args: proc_macro::TokenStream,
+ input: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+ let args = parse_macro_input!(args as AttributeArgs);
+ let input = parse_macro_input!(input as ItemStruct);
+ match xpcom_impl(args, input) {
+ Ok(ts) => ts.into(),
+ Err(err) => err.to_compile_error().into(),
+ }
+}