diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.rs | 484 |
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())) + } +} |