summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs')
-rw-r--r--toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs484
1 files changed, 484 insertions, 0 deletions
diff --git a/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs b/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs
new file mode 100644
index 0000000000..1418ccca29
--- /dev/null
+++ b/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs
@@ -0,0 +1,484 @@
+/* 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 std::{
+ cell::{Ref, RefCell},
+ convert::TryInto,
+ ffi::OsString,
+ mem,
+ path::PathBuf,
+ str,
+ sync::Arc,
+};
+
+use golden_gate::{ApplyTask, BridgedEngine, FerryTask};
+use moz_task::{self, DispatchOptions, TaskRunnable};
+use nserror::{nsresult, NS_OK};
+use nsstring::{nsACString, nsCString, nsString};
+use thin_vec::ThinVec;
+use webext_storage::STORAGE_VERSION;
+use xpcom::{
+ interfaces::{
+ mozIBridgedSyncEngineApplyCallback, mozIBridgedSyncEngineCallback,
+ mozIExtensionStorageCallback, mozIServicesLogSink, nsIFile, nsISerialEventTarget,
+ },
+ RefPtr,
+};
+
+use crate::error::{Error, Result};
+use crate::punt::{Punt, PuntTask, TeardownTask};
+use crate::store::{LazyStore, LazyStoreConfig};
+
+fn path_from_nsifile(file: &nsIFile) -> Result<PathBuf> {
+ let mut raw_path = nsString::new();
+ // `nsIFile::GetPath` gives us a UTF-16-encoded version of its
+ // native path, which we must turn back into a platform-native
+ // string. We can't use `nsIFile::nativePath()` here because
+ // it's marked as `nostdcall`, which Rust doesn't support.
+ unsafe { file.GetPath(&mut *raw_path) }.to_result()?;
+ let native_path = {
+ // On Windows, we can create a native string directly from the
+ // encoded path.
+ #[cfg(windows)]
+ {
+ use std::os::windows::prelude::*;
+ OsString::from_wide(&raw_path)
+ }
+ // On other platforms, we must first decode the raw path from
+ // UTF-16, and then create our native string.
+ #[cfg(not(windows))]
+ OsString::from(String::from_utf16(&raw_path)?)
+ };
+ Ok(native_path.into())
+}
+
+/// An XPCOM component class for the Rust extension storage API. This class
+/// implements the interfaces needed for syncing and storage.
+///
+/// This class can be created on any thread, but must not be shared between
+/// threads. In Rust terms, it's `Send`, but not `Sync`.
+#[xpcom(
+ implement(
+ mozIExtensionStorageArea,
+ mozIConfigurableExtensionStorageArea,
+ mozISyncedExtensionStorageArea,
+ mozIInterruptible,
+ mozIBridgedSyncEngine
+ ),
+ nonatomic
+)]
+pub struct StorageSyncArea {
+ /// A background task queue, used to run all our storage operations on a
+ /// thread pool. Using a serial event target here means that all operations
+ /// will execute sequentially.
+ queue: RefPtr<nsISerialEventTarget>,
+ /// The store is lazily initialized on the task queue the first time it's
+ /// used.
+ store: RefCell<Option<Arc<LazyStore>>>,
+}
+
+/// `mozIExtensionStorageArea` implementation.
+impl StorageSyncArea {
+ /// Creates a storage area and its task queue.
+ pub fn new() -> Result<RefPtr<StorageSyncArea>> {
+ let queue = moz_task::create_background_task_queue(cstr!("StorageSyncArea"))?;
+ Ok(StorageSyncArea::allocate(InitStorageSyncArea {
+ queue,
+ store: RefCell::new(Some(Arc::default())),
+ }))
+ }
+
+ /// Returns the store for this area, or an error if it's been torn down.
+ fn store(&self) -> Result<Ref<'_, Arc<LazyStore>>> {
+ let maybe_store = self.store.borrow();
+ if maybe_store.is_some() {
+ Ok(Ref::map(maybe_store, |s| s.as_ref().unwrap()))
+ } else {
+ Err(Error::AlreadyTornDown)
+ }
+ }
+
+ /// Dispatches a task for a storage operation to the task queue.
+ fn dispatch(&self, punt: Punt, callback: &mozIExtensionStorageCallback) -> Result<()> {
+ let name = punt.name();
+ let task = PuntTask::new(Arc::downgrade(&*self.store()?), punt, callback)?;
+ let runnable = TaskRunnable::new(name, Box::new(task))?;
+ // `may_block` schedules the runnable on a dedicated I/O pool.
+ TaskRunnable::dispatch_with_options(
+ runnable,
+ self.queue.coerce(),
+ DispatchOptions::new().may_block(true),
+ )?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ configure => Configure(
+ database_file: *const nsIFile,
+ kinto_file: *const nsIFile
+ )
+ );
+ /// Sets up the storage area.
+ fn configure(&self, database_file: &nsIFile, kinto_file: &nsIFile) -> Result<()> {
+ self.store()?.configure(LazyStoreConfig {
+ path: path_from_nsifile(database_file)?,
+ kinto_path: path_from_nsifile(kinto_file)?,
+ })?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ set => Set(
+ ext_id: *const ::nsstring::nsACString,
+ json: *const ::nsstring::nsACString,
+ callback: *const mozIExtensionStorageCallback
+ )
+ );
+ /// Sets one or more key-value pairs.
+ fn set(
+ &self,
+ ext_id: &nsACString,
+ json: &nsACString,
+ callback: &mozIExtensionStorageCallback,
+ ) -> Result<()> {
+ self.dispatch(
+ Punt::Set {
+ ext_id: str::from_utf8(ext_id)?.into(),
+ value: serde_json::from_str(str::from_utf8(json)?)?,
+ },
+ callback,
+ )?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ get => Get(
+ ext_id: *const ::nsstring::nsACString,
+ json: *const ::nsstring::nsACString,
+ callback: *const mozIExtensionStorageCallback
+ )
+ );
+ /// Gets values for one or more keys.
+ fn get(
+ &self,
+ ext_id: &nsACString,
+ json: &nsACString,
+ callback: &mozIExtensionStorageCallback,
+ ) -> Result<()> {
+ self.dispatch(
+ Punt::Get {
+ ext_id: str::from_utf8(ext_id)?.into(),
+ keys: serde_json::from_str(str::from_utf8(json)?)?,
+ },
+ callback,
+ )
+ }
+
+ xpcom_method!(
+ remove => Remove(
+ ext_id: *const ::nsstring::nsACString,
+ json: *const ::nsstring::nsACString,
+ callback: *const mozIExtensionStorageCallback
+ )
+ );
+ /// Removes one or more keys and their values.
+ fn remove(
+ &self,
+ ext_id: &nsACString,
+ json: &nsACString,
+ callback: &mozIExtensionStorageCallback,
+ ) -> Result<()> {
+ self.dispatch(
+ Punt::Remove {
+ ext_id: str::from_utf8(ext_id)?.into(),
+ keys: serde_json::from_str(str::from_utf8(json)?)?,
+ },
+ callback,
+ )
+ }
+
+ xpcom_method!(
+ clear => Clear(
+ ext_id: *const ::nsstring::nsACString,
+ callback: *const mozIExtensionStorageCallback
+ )
+ );
+ /// Removes all keys and values for the specified extension.
+ fn clear(&self, ext_id: &nsACString, callback: &mozIExtensionStorageCallback) -> Result<()> {
+ self.dispatch(
+ Punt::Clear {
+ ext_id: str::from_utf8(ext_id)?.into(),
+ },
+ callback,
+ )
+ }
+
+ xpcom_method!(
+ getBytesInUse => GetBytesInUse(
+ ext_id: *const ::nsstring::nsACString,
+ keys: *const ::nsstring::nsACString,
+ callback: *const mozIExtensionStorageCallback
+ )
+ );
+ /// Obtains the count of bytes in use for the specified key or for all keys.
+ fn getBytesInUse(
+ &self,
+ ext_id: &nsACString,
+ keys: &nsACString,
+ callback: &mozIExtensionStorageCallback,
+ ) -> Result<()> {
+ self.dispatch(
+ Punt::GetBytesInUse {
+ ext_id: str::from_utf8(ext_id)?.into(),
+ keys: serde_json::from_str(str::from_utf8(keys)?)?,
+ },
+ callback,
+ )
+ }
+
+ xpcom_method!(teardown => Teardown(callback: *const mozIExtensionStorageCallback));
+ /// Tears down the storage area, closing the backing database connection.
+ fn teardown(&self, callback: &mozIExtensionStorageCallback) -> Result<()> {
+ // Each storage task holds a `Weak` reference to the store, which it
+ // upgrades to an `Arc` (strong reference) when the task runs on the
+ // background queue. The strong reference is dropped when the task
+ // finishes. When we tear down the storage area, we relinquish our one
+ // owned strong reference to the `TeardownTask`. Because we're using a
+ // task queue, when the `TeardownTask` runs, it should have the only
+ // strong reference to the store, since all other tasks that called
+ // `Weak::upgrade` will have already finished. The `TeardownTask` can
+ // then consume the `Arc` and destroy the store.
+ let mut maybe_store = self.store.borrow_mut();
+ match mem::take(&mut *maybe_store) {
+ Some(store) => {
+ // Interrupt any currently-running statements.
+ store.interrupt();
+ // If dispatching the runnable fails, we'll leak the store
+ // without closing its database connection.
+ teardown(&self.queue, store, callback)?;
+ }
+ None => return Err(Error::AlreadyTornDown),
+ }
+ Ok(())
+ }
+
+ xpcom_method!(takeMigrationInfo => TakeMigrationInfo(callback: *const mozIExtensionStorageCallback));
+
+ /// Fetch-and-delete (e.g. `take`) information about the migration from the
+ /// kinto-based extension-storage to the rust-based storage.
+ fn takeMigrationInfo(&self, callback: &mozIExtensionStorageCallback) -> Result<()> {
+ self.dispatch(Punt::TakeMigrationInfo, callback)
+ }
+}
+
+fn teardown(
+ queue: &nsISerialEventTarget,
+ store: Arc<LazyStore>,
+ callback: &mozIExtensionStorageCallback,
+) -> Result<()> {
+ let task = TeardownTask::new(store, callback)?;
+ let runnable = TaskRunnable::new(TeardownTask::name(), Box::new(task))?;
+ TaskRunnable::dispatch_with_options(
+ runnable,
+ queue.coerce(),
+ DispatchOptions::new().may_block(true),
+ )?;
+ Ok(())
+}
+
+/// `mozISyncedExtensionStorageArea` implementation.
+impl StorageSyncArea {
+ xpcom_method!(
+ fetch_pending_sync_changes => FetchPendingSyncChanges(callback: *const mozIExtensionStorageCallback)
+ );
+ fn fetch_pending_sync_changes(&self, callback: &mozIExtensionStorageCallback) -> Result<()> {
+ self.dispatch(Punt::FetchPendingSyncChanges, callback)
+ }
+}
+
+/// `mozIInterruptible` implementation.
+impl StorageSyncArea {
+ xpcom_method!(
+ interrupt => Interrupt()
+ );
+ /// Interrupts any operations currently running on the background task
+ /// queue.
+ fn interrupt(&self) -> Result<()> {
+ self.store()?.interrupt();
+ Ok(())
+ }
+}
+
+/// `mozIBridgedSyncEngine` implementation.
+impl StorageSyncArea {
+ xpcom_method!(get_logger => GetLogger() -> *const mozIServicesLogSink);
+ fn get_logger(&self) -> Result<RefPtr<mozIServicesLogSink>> {
+ Err(NS_OK)?
+ }
+
+ xpcom_method!(set_logger => SetLogger(logger: *const mozIServicesLogSink));
+ fn set_logger(&self, _logger: Option<&mozIServicesLogSink>) -> Result<()> {
+ Ok(())
+ }
+
+ xpcom_method!(get_storage_version => GetStorageVersion() -> i32);
+ fn get_storage_version(&self) -> Result<i32> {
+ Ok(STORAGE_VERSION.try_into().unwrap())
+ }
+
+ // It's possible that migration, or even merging, will result in records
+ // too large for the server. We tolerate that (and hope that the addons do
+ // too :)
+ xpcom_method!(get_allow_skipped_record => GetAllowSkippedRecord() -> bool);
+ fn get_allow_skipped_record(&self) -> Result<bool> {
+ Ok(true)
+ }
+
+ xpcom_method!(
+ get_last_sync => GetLastSync(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn get_last_sync(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_last_sync(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ set_last_sync => SetLastSync(
+ last_sync_millis: i64,
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn set_last_sync(
+ &self,
+ last_sync_millis: i64,
+ callback: &mozIBridgedSyncEngineCallback,
+ ) -> Result<()> {
+ Ok(
+ FerryTask::for_set_last_sync(self.new_bridge()?, last_sync_millis, callback)?
+ .dispatch(&self.queue)?,
+ )
+ }
+
+ xpcom_method!(
+ get_sync_id => GetSyncId(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn get_sync_id(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_sync_id(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ reset_sync_id => ResetSyncId(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn reset_sync_id(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_reset_sync_id(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ ensure_current_sync_id => EnsureCurrentSyncId(
+ new_sync_id: *const nsACString,
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn ensure_current_sync_id(
+ &self,
+ new_sync_id: &nsACString,
+ callback: &mozIBridgedSyncEngineCallback,
+ ) -> Result<()> {
+ Ok(
+ FerryTask::for_ensure_current_sync_id(self.new_bridge()?, new_sync_id, callback)?
+ .dispatch(&self.queue)?,
+ )
+ }
+
+ xpcom_method!(
+ sync_started => SyncStarted(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn sync_started(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_sync_started(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ store_incoming => StoreIncoming(
+ incoming_envelopes_json: *const ThinVec<::nsstring::nsCString>,
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn store_incoming(
+ &self,
+ incoming_envelopes_json: Option<&ThinVec<nsCString>>,
+ callback: &mozIBridgedSyncEngineCallback,
+ ) -> Result<()> {
+ Ok(FerryTask::for_store_incoming(
+ self.new_bridge()?,
+ incoming_envelopes_json.map(|v| v.as_slice()).unwrap_or(&[]),
+ callback,
+ )?
+ .dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(apply => Apply(callback: *const mozIBridgedSyncEngineApplyCallback));
+ fn apply(&self, callback: &mozIBridgedSyncEngineApplyCallback) -> Result<()> {
+ Ok(ApplyTask::new(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ set_uploaded => SetUploaded(
+ server_modified_millis: i64,
+ uploaded_ids: *const ThinVec<::nsstring::nsCString>,
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn set_uploaded(
+ &self,
+ server_modified_millis: i64,
+ uploaded_ids: Option<&ThinVec<nsCString>>,
+ callback: &mozIBridgedSyncEngineCallback,
+ ) -> Result<()> {
+ Ok(FerryTask::for_set_uploaded(
+ self.new_bridge()?,
+ server_modified_millis,
+ uploaded_ids.map(|v| v.as_slice()).unwrap_or(&[]),
+ callback,
+ )?
+ .dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ sync_finished => SyncFinished(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn sync_finished(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_sync_finished(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ reset => Reset(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn reset(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_reset(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ xpcom_method!(
+ wipe => Wipe(
+ callback: *const mozIBridgedSyncEngineCallback
+ )
+ );
+ fn wipe(&self, callback: &mozIBridgedSyncEngineCallback) -> Result<()> {
+ Ok(FerryTask::for_wipe(self.new_bridge()?, callback)?.dispatch(&self.queue)?)
+ }
+
+ fn new_bridge(&self) -> Result<Box<dyn BridgedEngine>> {
+ Ok(Box::new(self.store()?.get()?.bridged_engine()))
+ }
+}