/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Component interface implementation. //! //! This module implements the `nsIKeyValue` XPCOM interfaces that are //! exposed to C++ and chrome JS callers.
use std::{io, ops::Bound, sync::Arc};
use atomic_refcell::AtomicRefCell; use crossbeam_utils::atomic::AtomicCell; use nserror::{nsresult, NS_OK}; use nsstring::{nsACString, nsAString, nsCString, nsString}; use rkv::{
backend::{SafeMode as RkvSafeMode, SafeModeEnvironment as RkvSafeModeEnvironment},
Rkv,
}; use storage_variant::{HashPropertyBag, VariantType}; use thin_vec::ThinVec; use xpcom::{
getter_addrefs,
interfaces::{
nsIAsyncShutdownClient, nsIAsyncShutdownService, nsIKeyValueDatabaseCallback,
nsIKeyValueDatabaseImportOptions, nsIKeyValueEnumeratorCallback,
nsIKeyValueImportSourceSpec, nsIKeyValueImporter, nsIKeyValuePair,
nsIKeyValueVariantCallback, nsIKeyValueVoidCallback, nsIPropertyBag, nsIVariant,
},
xpcom, xpcom_method, RefPtr,
};
xpcom_method!(
is_empty => IsEmpty(
callback: *const nsIKeyValueVariantCallback
)
); fn is_empty(&self, callback: &nsIKeyValueVariantCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request =
moz_task::spawn_blocking("skv:KeyValueDatabase:IsEmpty:Request", asyncmove { let store = store?; let db = Database::new(&store, &name);
Ok(db.is_empty()?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:IsEmpty:Response", asyncmove { match signal.aborting(request).await {
Ok(b) => unsafe { callback.Resolve(b.into_variant().coerce()) },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("isEmpty: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
count => Count(
callback: *const nsIKeyValueVariantCallback
)
); fn count(&self, callback: &nsIKeyValueVariantCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Count:Request", asyncmove { let store = store?; let db = Database::new(&store, &name);
Ok(db.count()?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Count:Response", asyncmove { match signal.aborting(request).await {
Ok(n) => unsafe { callback.Resolve(n.into_variant().coerce()) },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("count: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
size => Size(
callback: *const nsIKeyValueVariantCallback
)
); fn size(&self, callback: &nsIKeyValueVariantCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Size:Request", asyncmove { let store = store?; let db = Database::new(&store, &name);
Ok(db.size()?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Size:Response", asyncmove { match signal.aborting(request).await {
Ok(n) => unsafe { callback.Resolve(n.into_variant().coerce()) },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("size: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
put => Put(
callback: *const nsIKeyValueVoidCallback,
key: *const nsACString,
value: *const nsIVariant
)
); fn put(
&self,
callback: &nsIKeyValueVoidCallback,
key: &nsACString,
value: &nsIVariant,
) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key); let value = Value::from_variant(value)?;
Ok((store, key, value))
}();
let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Put:Request", asyncmove{ let (store, key, value) = inputs?; let db = Database::new(&store, &name);
Ok(db.put(&[(key, value)])?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Put:Response", asyncmove { match signal.aborting(request).await {
Ok(()) => unsafe { callback.Resolve() },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("put: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
let name = self.name.clone(); let request =
moz_task::spawn_blocking("skv:KeyValueDatabase:WriteMany:Request", asyncmove { let (store, pairs) = inputs?; let db = Database::new(&store, &name);
Ok(db.put(pairs.as_slice())?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:WriteMany:Response", asyncmove { match signal.aborting(request).await {
Ok(()) => unsafe { callback.Resolve() },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("writeMany: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
get => Get(
callback: *const nsIKeyValueVariantCallback,
key: *const nsACString,
default_value: *const nsIVariant
)
); fn get(
&self,
callback: &nsIKeyValueVariantCallback,
key: &nsACString,
default_value: &nsIVariant,
) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key);
Ok((store, key))
}();
let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Get:Request", asyncmove{ let (store, key) = inputs?; let db = Database::new(&store, &name);
Ok(db.get(&key, &GetOptions::new())?)
});
let signal = self.client.signal(); let default_value = RefPtr::new(default_value); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Get:Response", asyncmove { let result = signal.aborting(request).await.and_then(|value| {
Ok(match value {
Some(value) => value.to_variant()?,
None => default_value,
})
}); match result {
Ok(variant) => unsafe { callback.Resolve(variant.coerce()) },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("get: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
has => Has(callback: *const nsIKeyValueVariantCallback, key: *const nsACString)
); fn has(
&self,
callback: &nsIKeyValueVariantCallback,
key: &nsACString,
) -> Result<(), Infallible> { let inputs = || -> Result<_, InterfaceError> { let store = self.store()?; let key = Key::from(key);
Ok((store, key))
}();
let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Has:Request", asyncmove{ let (store, key) = inputs?; let db = Database::new(&store, &name);
Ok(db.has(&key, &GetOptions::new())?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Has:Response", asyncmove { match signal.aborting(request).await {
Ok(b) => unsafe { callback.Resolve(b.into_variant().coerce()) },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("has: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Delete:Request", asyncmove { let (store, key) = inputs?; let db = Database::new(&store, &name);
Ok(db.delete(&key)?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Delete:Response", asyncmove { match signal.aborting(request).await {
Ok(()) => unsafe { callback.Resolve() },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("delete: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
let name = self.name.clone(); let request =
moz_task::spawn_blocking("skv:KeyValueDatabase:DeleteRange:Request", asyncmove { let (store, from_key, to_key) = inputs?; let db = Database::new(&store, &name);
Ok(db.delete_range((from_key, to_key))?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:DeleteRange:Response", asyncmove { match signal.aborting(request).await {
Ok(()) => unsafe { callback.Resolve() },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("deleteRange: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
clear => Clear(callback: *const nsIKeyValueVoidCallback)
); fn clear(&self, callback: &nsIKeyValueVoidCallback) -> Result<(), Infallible> { let store = self.store(); let name = self.name.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Clear:Request", asyncmove { let store = store?; let db = Database::new(&store, &name);
Ok(db.clear()?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Clear:Response", asyncmove { match signal.aborting(request).await {
Ok(()) => unsafe { callback.Resolve() },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("clear: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
let name = self.name.clone(); let request =
moz_task::spawn_blocking("skv:KeyValueDatabase:Enumerate:Request", asyncmove { let (store, from_key, to_key) = inputs?; let db = Database::new(&store, &name);
Ok(db.enumerate((from_key, to_key), GetOptions::new().concurrent(true))?)
});
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local("skv:KeyValueDatabase:Enumerate:Response", asyncmove { match signal.aborting(request).await {
Ok(pairs) => { let enumerator = KeyValueEnumerator::new(pairs); unsafe { callback.Resolve(enumerator.coerce()) }
}
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("enumerate: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
})
.detach();
Ok(())
}
xpcom_method!(
close => Close(
callback: *const nsIKeyValueVoidCallback
)
); fn close(&self, callback: &nsIKeyValueVoidCallback) -> Result<(), Infallible> { let client = self.client.clone(); let request = moz_task::spawn_blocking("skv:KeyValueDatabase:Close:Request", asyncmove {
client.invalidate()
});
/// Registers a blocker to close all open databases on shutdown. /// /// This function may be called on any thread, but the blocker itself is /// registered on the main thread, because `nsIAsyncShutdownService` is /// main-thread-only. fn register(client: CoordinatorClient<'static>) { let Ok(main_thread) = moz_task::get_main_thread() else { return;
};
moz_task::spawn_onto( "skv:KeyValueServiceShutdownBlocker:Register",
main_thread.coerce(), asyncmove { let _ = || -> Result<(), InterfaceError> { let service =
xpcom::components::AsyncShutdown::service::<nsIAsyncShutdownService>()?; let barrier = getter_addrefs(|p| unsafe { service.GetProfileBeforeChange(p) })?; let blocker = Self::new(client); unsafe {
barrier.AddBlocker(
blocker.coerce(),
&*nsString::from(file!()),
line!() as i32,
&*nsString::new(),
)
}
.to_result()?;
Ok(())
}();
},
)
.detach();
}
xpcom_method!(
import => Import(
callback: *const nsIKeyValueVoidCallback
)
); fn import(&self, callback: &nsIKeyValueVoidCallback) -> Result<(), Infallible> { let client = self.client.clone(); let specs = self.specs.borrow().clone(); let request = moz_task::spawn_blocking( "skv:RkvSafeModeKeyValueImporter:Import:Request", asyncmove { letmut manager = rkv::Manager::<RkvSafeModeEnvironment>::singleton()
.write()
.unwrap(); let store =
client.store_for_path(StorePath::canonicalizing(specs[0].dir.clone())?)?;
// An Rkv environment is unique to a storage directory; // if we're importing from multiple directories, we need to // open an environment for each directory. let rkvs = { letmut rkvs = Vec::with_capacity(specs.len()); for spec in specs { let Some(dbs) = spec.to_source_databases() else { continue;
}; let dir = spec.dir.canonicalize()?; let builder = Rkv::environment_builder::<RkvSafeMode>(); let rkv = manager.get_or_create_from_builder(
dir.as_path(),
builder,
Rkv::from_builder::<RkvSafeMode>,
)?;
rkvs.push((rkv, dbs));
}
rkvs
};
// Import all databases from each directory, in order. for (rkv, dbs) in rkvs { let env = rkv.read().unwrap(); let importer = RkvImporter::new(&*env, &store, dbs)?;
importer.import()?;
importer.cleanup();
}
Ok(())
},
);
let signal = self.client.signal(); let callback = RefPtr::new(callback);
moz_task::spawn_local( "skv:RkvSafeModeKeyValueImporter:Import:Response", asyncmove { match signal.aborting(request).await {
Ok(()) => unsafe { callback.Resolve() },
Err(InterfaceError::Abort(_)) => unsafe {
callback.Reject(&*nsCString::from("import: aborted"))
},
Err(err) => unsafe { callback.Reject(&*nsCString::from(err.to_string())) },
}
},
)
.detach();
/// The error type for interface methods that never return an error. /// /// This is equivalent to [`std::convert::Infallible`], but implements /// `Into<nsresult>`, which we can't implement on [`std::convert::Infallible`] /// because of the orphan rule. enum Infallible {}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.