summaryrefslogtreecommitdiffstats
path: root/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt
diff options
context:
space:
mode:
Diffstat (limited to 'services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt')
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs109
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs14
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs124
-rw-r--r--services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs523
4 files changed, 770 insertions, 0 deletions
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs
new file mode 100644
index 0000000000..0328e15aae
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/error.rs
@@ -0,0 +1,109 @@
+/* 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::{error, fmt, result, str::Utf8Error, sync::PoisonError};
+
+use nserror::{
+ nsresult, NS_ERROR_ALREADY_INITIALIZED, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG,
+ NS_ERROR_NOT_AVAILABLE, NS_ERROR_UNEXPECTED,
+};
+
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(Debug)]
+pub enum Error {
+ /// A wrapped FxA error.
+ FxAError(fxa_client::Error),
+
+ /// A wrapped XPCOM error.
+ Nsresult(nsresult),
+
+ /// A punt already ran.
+ AlreadyRan(&'static str),
+
+ /// A punt didn't run on the background task queue.
+ DidNotRun(&'static str),
+
+ /// A punt was already torn down when used.
+ AlreadyTornDown,
+
+ /// A Gecko string couldn't be converted to UTF-8.
+ MalformedString(Box<dyn error::Error + Send + Sync + 'static>),
+
+ /// The FxA object has already been initialized.
+ AlreadyInitialized,
+
+ /// The FxA object has not been initialized before use.
+ Unavailable,
+
+ /// Failure acquiring a lock on the FxA object in the background thread.
+ PoisonError,
+}
+
+impl error::Error for Error {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match self {
+ Error::MalformedString(error) => Some(error.as_ref()),
+ _ => None,
+ }
+ }
+}
+
+impl From<Error> for nsresult {
+ fn from(error: Error) -> nsresult {
+ match error {
+ Error::FxAError(_) => NS_ERROR_FAILURE,
+ Error::AlreadyRan(_)
+ | Error::DidNotRun(_)
+ | Error::AlreadyTornDown
+ | Error::PoisonError => NS_ERROR_UNEXPECTED,
+ Error::Nsresult(result) => result,
+ Error::MalformedString(_) => NS_ERROR_INVALID_ARG,
+ Error::AlreadyInitialized => NS_ERROR_ALREADY_INITIALIZED,
+ Error::Unavailable => NS_ERROR_NOT_AVAILABLE,
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::FxAError(error) => error.fmt(f),
+ Error::Nsresult(result) => write!(f, "Operation failed with {}", result.error_name()),
+ Error::AlreadyRan(what) => write!(f, "`{}` already ran on the background thread", what),
+ Error::DidNotRun(what) => write!(f, "Failed to run `{}` on background thread", what),
+ Error::AlreadyTornDown => {
+ write!(f, "Can't use a storage area that's already torn down")
+ }
+ Error::MalformedString(error) => error.fmt(f),
+ Error::AlreadyInitialized => f.write_str("The resource has already been initialized"),
+ Error::Unavailable => f.write_str("A resource for this operation is unavailable"),
+ Error::PoisonError => f.write_str("Error getting read/write lock"),
+ }
+ }
+}
+
+impl From<nsresult> for Error {
+ fn from(result: nsresult) -> Error {
+ Error::Nsresult(result)
+ }
+}
+
+impl From<Utf8Error> for Error {
+ fn from(error: Utf8Error) -> Error {
+ Error::MalformedString(error.into())
+ }
+}
+
+impl<T> From<PoisonError<T>> for Error {
+ fn from(_error: PoisonError<T>) -> Error {
+ Error::PoisonError
+ }
+}
+
+impl From<fxa_client::error::Error> for Error {
+ fn from(result: fxa_client::error::Error) -> Error {
+ Error::FxAError(result)
+ }
+}
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs
new file mode 100644
index 0000000000..3a6cd63cbe
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/mod.rs
@@ -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/. */
+
+//! Punt is pretty much a copy-paste of the Golden Gate crate in services/sync/golden_gate
+//! but specialized to work on the fxa-client crate.
+//! In short it helps run Rust code in a background thread, handle errors and get the results back.
+//! There's an effort to factorize these helpers in https://bugzilla.mozilla.org/show_bug.cgi?id=1626703.
+
+pub mod error;
+mod punt;
+mod task;
+
+pub use task::PuntTask;
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs
new file mode 100644
index 0000000000..79c16ee49f
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/punt.rs
@@ -0,0 +1,124 @@
+/* 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 fxa_client::device::{
+ Capability as FxaDeviceCapability, PushSubscription as FxAPushSubscription,
+ Type as FxaDeviceType,
+};
+use nsstring::nsCString;
+use storage_variant::VariantType;
+use xpcom::{interfaces::nsIVariant, RefPtr};
+
+/// An operation that runs on the background thread, and optionally passes a
+/// result to its callback.
+pub enum Punt {
+ ToJson,
+ BeginOAuthFlow(Vec<String>, String),
+ CompleteOAuthFlow(String, String),
+ Disconnect,
+ GetAccessToken(String, Option<u64>),
+ GetSessionToken,
+ GetAttachedClients,
+ CheckAuthorizationStatus,
+ ClearAccessTokenCache,
+ HandleSessionTokenChange(String),
+ MigrateFromSessionToken(String, String, String, bool),
+ RetryMigrateFromSessionToken,
+ IsInMigrationState,
+ GetProfile(bool),
+ GetTokenServerEndpointUrl,
+ GetConnectionSuccessUrl,
+ GetManageAccountUrl(String),
+ GetManageDevicesUrl(String),
+ FetchDevices(bool),
+ SetDeviceDisplayName(String),
+ HandlePushMessage(String),
+ PollDeviceCommands,
+ SendSingleTab(String, String, String),
+ SetDevicePushSubscription(FxAPushSubscription),
+ InitializeDevice(String, FxaDeviceType, Vec<FxaDeviceCapability>),
+ EnsureCapabilities(Vec<FxaDeviceCapability>),
+}
+
+impl Punt {
+ /// Returns the operation name for debugging and labeling the task
+ /// runnable.
+ pub fn name(&self) -> &'static str {
+ match self {
+ Punt::ToJson => concat!(module_path!(), "toJson"),
+ Punt::BeginOAuthFlow { .. } => concat!(module_path!(), "beginOAuthFlow"),
+ Punt::CompleteOAuthFlow { .. } => concat!(module_path!(), "completeOAuthFlow"),
+ Punt::Disconnect => concat!(module_path!(), "disconnect"),
+ Punt::GetAccessToken { .. } => concat!(module_path!(), "getAccessToken"),
+ Punt::GetSessionToken => concat!(module_path!(), "getSessionToken"),
+ Punt::GetAttachedClients => concat!(module_path!(), "getAttachedClients"),
+ Punt::CheckAuthorizationStatus => concat!(module_path!(), "checkAuthorizationStatus"),
+ Punt::ClearAccessTokenCache => concat!(module_path!(), "clearAccessTokenCache"),
+ Punt::HandleSessionTokenChange { .. } => {
+ concat!(module_path!(), "handleSessionTokenChange")
+ }
+ Punt::MigrateFromSessionToken { .. } => {
+ concat!(module_path!(), "migrateFromSessionToken")
+ }
+ Punt::RetryMigrateFromSessionToken => {
+ concat!(module_path!(), "retryMigrateFromSessionToken")
+ }
+ Punt::IsInMigrationState => concat!(module_path!(), "isInMigrationState"),
+ Punt::GetProfile { .. } => concat!(module_path!(), "getProfile"),
+ Punt::GetTokenServerEndpointUrl => concat!(module_path!(), "getTokenServerEndpointUrl"),
+ Punt::GetConnectionSuccessUrl => concat!(module_path!(), "getConnectionSuccessUrl"),
+ Punt::GetManageAccountUrl { .. } => concat!(module_path!(), "getManageAccountUrl"),
+ Punt::GetManageDevicesUrl { .. } => concat!(module_path!(), "getManageDevicesUrl"),
+ Punt::FetchDevices { .. } => concat!(module_path!(), "fetchDevices"),
+ Punt::SetDeviceDisplayName { .. } => concat!(module_path!(), "setDeviceDisplayName"),
+ Punt::HandlePushMessage { .. } => concat!(module_path!(), "handlePushMessage"),
+ Punt::PollDeviceCommands => concat!(module_path!(), "pollDeviceCommands"),
+ Punt::SendSingleTab { .. } => concat!(module_path!(), "sendSingleTab"),
+ Punt::SetDevicePushSubscription { .. } => {
+ concat!(module_path!(), "setDevicePushSubscription")
+ }
+ Punt::InitializeDevice { .. } => concat!(module_path!(), "initializeDevice"),
+ Punt::EnsureCapabilities { .. } => concat!(module_path!(), "ensureCapabilities"),
+ }
+ }
+}
+
+/// The result of a punt task, sent from the background thread back to the
+/// main thread. Results are converted to variants, and passed as arguments to
+/// the callbacks.
+pub enum PuntResult {
+ String(String),
+ Boolean(bool),
+ Null,
+}
+
+impl PuntResult {
+ pub fn url_spec(url: url::Url) -> Self {
+ PuntResult::String(url.into_string())
+ }
+ pub fn json_stringify<T>(value: T) -> Self
+ where
+ T: serde::Serialize + std::marker::Sized,
+ {
+ PuntResult::String(serde_json::to_string(&value).unwrap())
+ }
+}
+
+impl From<()> for PuntResult {
+ fn from(_: ()) -> PuntResult {
+ PuntResult::Null
+ }
+}
+
+impl PuntResult {
+ /// Converts the result to an `nsIVariant` that can be passed as an
+ /// argument to `callback.handleResult()`.
+ pub fn into_variant(self) -> RefPtr<nsIVariant> {
+ match self {
+ PuntResult::String(v) => nsCString::from(v).into_variant(),
+ PuntResult::Boolean(b) => b.into_variant(),
+ PuntResult::Null => ().into_variant(),
+ }
+ }
+}
diff --git a/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs
new file mode 100644
index 0000000000..2e2c8ad68e
--- /dev/null
+++ b/services/fxaccounts/rust-bridge/firefox-accounts-bridge/src/punt/task.rs
@@ -0,0 +1,523 @@
+/* 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::punt::{
+ error::{self, Error},
+ punt::{Punt, PuntResult},
+};
+use atomic_refcell::AtomicRefCell;
+use fxa_client::{
+ device::{
+ Capability as FxaDeviceCapability, CommandFetchReason,
+ PushSubscription as FxaPushSubscription, Type as FxaDeviceType,
+ },
+ FirefoxAccount,
+};
+use moz_task::{DispatchOptions, Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder};
+use nserror::nsresult;
+use nsstring::{nsACString, nsCString};
+use std::{
+ fmt::Write,
+ mem, str,
+ sync::{Arc, Mutex, Weak},
+};
+use xpcom::{
+ interfaces::{mozIFirefoxAccountsBridgeCallback, nsIEventTarget},
+ RefPtr,
+};
+
+/// A punt task sends an operation to an fxa instance on a
+/// background thread or task queue, and ferries back an optional result to
+/// a callback.
+pub struct PuntTask {
+ name: &'static str,
+ fxa: Weak<Mutex<FirefoxAccount>>,
+ punt: AtomicRefCell<Option<Punt>>,
+ callback: ThreadPtrHandle<mozIFirefoxAccountsBridgeCallback>,
+ result: AtomicRefCell<Result<PuntResult, Error>>,
+}
+
+impl PuntTask {
+ /// Creates a task for a punt. The `callback` is bound to the current
+ /// thread, and will be called once, after the punt returns from the
+ /// background thread.
+ fn new(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ punt: Punt,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let name = punt.name();
+ Ok(Self {
+ name: punt.name(),
+ fxa: Arc::downgrade(fxa),
+ punt: AtomicRefCell::new(Some(punt)),
+ callback: ThreadPtrHolder::new(
+ cstr!("mozIFirefoxAccountsBridgeCallback"),
+ RefPtr::new(callback),
+ )?,
+ result: AtomicRefCell::new(Err(Error::DidNotRun(name).into())),
+ })
+ }
+
+ /// Creates a task that calls begin_oauth_flow.
+ #[inline]
+ pub fn for_begin_oauth_flow(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ scopes: &[nsCString],
+ entry_point: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let scopes = scopes.iter().try_fold(
+ Vec::with_capacity(scopes.len()),
+ |mut acc, scope| -> error::Result<_> {
+ acc.push(std::str::from_utf8(&*scope)?.into());
+ Ok(acc)
+ },
+ )?;
+ let entry_point = str::from_utf8(&*entry_point)?.into();
+ Self::new(fxa, Punt::BeginOAuthFlow(scopes, entry_point), callback)
+ }
+
+ /// Creates a task that calls complete_oauth_flow.
+ #[inline]
+ pub fn for_complete_oauth_flow(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ code: &nsACString,
+ state: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let code = str::from_utf8(&*code)?.into();
+ let state = str::from_utf8(&*state)?.into();
+ Self::new(fxa, Punt::CompleteOAuthFlow(code, state), callback)
+ }
+
+ /// Creates a task that calls disconnect.
+ #[inline]
+ pub fn for_disconnect(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::Disconnect, callback)
+ }
+
+ /// Creates a task that calls to_json.
+ #[inline]
+ pub fn for_to_json(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::ToJson, callback)
+ }
+
+ /// Creates a task that calls get_access_token.
+ #[inline]
+ pub fn for_get_access_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ scope: &nsACString,
+ ttl: u64,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let scope = str::from_utf8(&*scope)?.into();
+ let ttl = if ttl > 0 { Some(ttl) } else { None };
+ Self::new(fxa, Punt::GetAccessToken(scope, ttl), callback)
+ }
+
+ /// Creates a task that calls get_session_token.
+ #[inline]
+ pub fn for_get_session_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetSessionToken, callback)
+ }
+
+ /// Creates a task that calls get_attached_clients.
+ #[inline]
+ pub fn for_get_attached_clients(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetAttachedClients, callback)
+ }
+
+ /// Creates a task that calls check_authorization_status.
+ #[inline]
+ pub fn for_check_authorization_status(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::CheckAuthorizationStatus, callback)
+ }
+
+ /// Creates a task that calls clear_access_token_cache.
+ #[inline]
+ pub fn for_clear_access_token_cache(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::ClearAccessTokenCache, callback)
+ }
+
+ /// Creates a task that calls handle_session_token_change.
+ #[inline]
+ pub fn for_handle_session_token_change(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ session_token: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let session_token = str::from_utf8(&*session_token)?.into();
+ Self::new(fxa, Punt::HandleSessionTokenChange(session_token), callback)
+ }
+
+ /// Creates a task that calls migrate_from_session_token.
+ #[inline]
+ pub fn for_migrate_from_session_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ session_token: &nsACString,
+ k_sync: &nsACString,
+ k_xcs: &nsACString,
+ copy_session_token: bool,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let session_token = str::from_utf8(&*session_token)?.into();
+ let k_sync = str::from_utf8(&*k_sync)?.into();
+ let k_xcs = str::from_utf8(&*k_xcs)?.into();
+ Self::new(
+ fxa,
+ Punt::MigrateFromSessionToken(session_token, k_sync, k_xcs, copy_session_token),
+ callback,
+ )
+ }
+
+ /// Creates a task that calls retry_migrate_from_session_token.
+ #[inline]
+ pub fn for_retry_migrate_from_session_token(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::RetryMigrateFromSessionToken, callback)
+ }
+
+ /// Creates a task that calls is_in_migration_state.
+ #[inline]
+ pub fn for_is_in_migration_state(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::IsInMigrationState, callback)
+ }
+
+ /// Creates a task that calls get_profile.
+ #[inline]
+ pub fn for_get_profile(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ ignore_cache: bool,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetProfile(ignore_cache), callback)
+ }
+
+ /// Creates a task that calls get_token_server_endpoint_url.
+ #[inline]
+ pub fn for_get_token_server_endpoint_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetTokenServerEndpointUrl, callback)
+ }
+
+ /// Creates a task that calls get_connection_success_url.
+ #[inline]
+ pub fn for_get_connection_success_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::GetConnectionSuccessUrl, callback)
+ }
+
+ /// Creates a task that calls get_manage_account_url.
+ #[inline]
+ pub fn for_get_manage_account_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ entrypoint: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let entrypoint = str::from_utf8(&*entrypoint)?.into();
+ Self::new(fxa, Punt::GetManageAccountUrl(entrypoint), callback)
+ }
+
+ /// Creates a task that calls get_manage_devices_url.
+ #[inline]
+ pub fn for_get_manage_devices_url(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ entrypoint: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let entrypoint = str::from_utf8(&*entrypoint)?.into();
+ Self::new(fxa, Punt::GetManageDevicesUrl(entrypoint), callback)
+ }
+
+ /// Creates a task that calls fetch_devices.
+ #[inline]
+ pub fn for_fetch_devices(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ ignore_cache: bool,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::FetchDevices(ignore_cache), callback)
+ }
+
+ /// Creates a task that calls set_device_display_name.
+ #[inline]
+ pub fn for_set_device_display_name(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ name: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let name = str::from_utf8(&*name)?.into();
+ Self::new(fxa, Punt::SetDeviceDisplayName(name), callback)
+ }
+
+ /// Creates a task that calls handle_push_message.
+ #[inline]
+ pub fn for_handle_push_message(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ payload: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let payload = str::from_utf8(&*payload)?.into();
+ Self::new(fxa, Punt::HandlePushMessage(payload), callback)
+ }
+
+ /// Creates a task that calls poll_device_commands.
+ #[inline]
+ pub fn for_poll_device_commands(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ Self::new(fxa, Punt::PollDeviceCommands, callback)
+ }
+
+ /// Creates a task that calls send_single_tab.
+ #[inline]
+ pub fn for_send_single_tab(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ target_id: &nsACString,
+ title: &nsACString,
+ url: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let target_id = str::from_utf8(&*target_id)?.into();
+ let title = str::from_utf8(&*title)?.into();
+ let url = str::from_utf8(&*url)?.into();
+ Self::new(fxa, Punt::SendSingleTab(target_id, title, url), callback)
+ }
+
+ /// Creates a task that calls set_device_push_subscription.
+ #[inline]
+ pub fn for_set_device_push_subscription(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ endpoint: &nsACString,
+ public_key: &nsACString,
+ auth_key: &nsACString,
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let push_subscription = FxaPushSubscription {
+ endpoint: str::from_utf8(&*endpoint)?.into(),
+ public_key: str::from_utf8(&*public_key)?.into(),
+ auth_key: str::from_utf8(&*auth_key)?.into(),
+ };
+ Self::new(
+ fxa,
+ Punt::SetDevicePushSubscription(push_subscription),
+ callback,
+ )
+ }
+
+ /// Creates a task that calls initialize_device.
+ #[inline]
+ pub fn for_initialize_device(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ name: &nsACString,
+ device_type: &nsACString,
+ supported_capabilities: &[nsCString],
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let name = str::from_utf8(&*name)?.into();
+ let device_type = to_device_type(device_type)?;
+ let supported_capabilities = to_capabilities(supported_capabilities)?;
+ Self::new(
+ fxa,
+ Punt::InitializeDevice(name, device_type, supported_capabilities),
+ callback,
+ )
+ }
+
+ /// Creates a task that calls ensure_capabilities.
+ #[inline]
+ pub fn for_ensure_capabilities(
+ fxa: &Arc<Mutex<FirefoxAccount>>,
+ supported_capabilities: &[nsCString],
+ callback: &mozIFirefoxAccountsBridgeCallback,
+ ) -> error::Result<PuntTask> {
+ let supported_capabilities = to_capabilities(supported_capabilities)?;
+ Self::new(
+ fxa,
+ Punt::EnsureCapabilities(supported_capabilities),
+ callback,
+ )
+ }
+
+ /// Dispatches the task to the given thread `target`.
+ pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), Error> {
+ let runnable = TaskRunnable::new(self.name, Box::new(self))?;
+ // `may_block` schedules the task on the I/O thread pool, since we
+ // expect most operations to wait on I/O.
+ TaskRunnable::dispatch_with_options(
+ runnable,
+ target,
+ DispatchOptions::default().may_block(true),
+ )?;
+ Ok(())
+ }
+
+ fn run_with_punt(&self, punt: Punt) -> Result<PuntResult, Error> {
+ let fxa = self.fxa.upgrade().ok_or_else(|| Error::AlreadyTornDown)?;
+ let mut fxa = fxa.lock()?;
+ Ok(match punt {
+ Punt::ToJson => fxa.to_json().map(PuntResult::String),
+ Punt::BeginOAuthFlow(scopes, entry_point) => {
+ let scopes: Vec<&str> = scopes.iter().map(AsRef::as_ref).collect();
+ fxa.begin_oauth_flow(&scopes, &entry_point, None)
+ .map(PuntResult::String)
+ }
+ Punt::CompleteOAuthFlow(code, state) => fxa
+ .complete_oauth_flow(&code, &state)
+ .map(|_| PuntResult::Null),
+ Punt::Disconnect => {
+ fxa.disconnect();
+ Ok(PuntResult::Null)
+ }
+ Punt::GetAccessToken(scope, ttl) => fxa
+ .get_access_token(&scope, ttl)
+ .map(PuntResult::json_stringify),
+ Punt::GetSessionToken => fxa.get_session_token().map(PuntResult::String),
+ Punt::GetAttachedClients => fxa.get_attached_clients().map(PuntResult::json_stringify),
+ Punt::CheckAuthorizationStatus => fxa
+ .check_authorization_status()
+ .map(PuntResult::json_stringify),
+ Punt::ClearAccessTokenCache => {
+ fxa.clear_access_token_cache();
+ Ok(PuntResult::Null)
+ }
+ Punt::HandleSessionTokenChange(session_token) => fxa
+ .handle_session_token_change(&session_token)
+ .map(|_| PuntResult::Null),
+ Punt::MigrateFromSessionToken(session_token, k_sync, k_xcs, copy_session_token) => fxa
+ .migrate_from_session_token(&session_token, &k_sync, &k_xcs, copy_session_token)
+ .map(PuntResult::json_stringify),
+ Punt::RetryMigrateFromSessionToken => {
+ fxa.try_migration().map(PuntResult::json_stringify)
+ }
+ Punt::IsInMigrationState => {
+ Ok(PuntResult::Boolean(match fxa.is_in_migration_state() {
+ fxa_client::migrator::MigrationState::None => false,
+ _ => true,
+ }))
+ }
+ Punt::GetProfile(ignore_cache) => fxa
+ .get_profile(ignore_cache)
+ .map(PuntResult::json_stringify),
+ Punt::GetTokenServerEndpointUrl => fxa
+ .get_token_server_endpoint_url()
+ .map(PuntResult::url_spec),
+ Punt::GetConnectionSuccessUrl => {
+ fxa.get_connection_success_url().map(PuntResult::url_spec)
+ }
+ Punt::GetManageAccountUrl(entrypoint) => fxa
+ .get_manage_account_url(&entrypoint)
+ .map(PuntResult::url_spec),
+ Punt::GetManageDevicesUrl(entrypoint) => fxa
+ .get_manage_devices_url(&entrypoint)
+ .map(PuntResult::url_spec),
+ Punt::FetchDevices(ignore_cache) => fxa
+ .get_devices(ignore_cache)
+ .map(PuntResult::json_stringify),
+ Punt::SetDeviceDisplayName(name) => {
+ fxa.set_device_name(&name).map(PuntResult::json_stringify)
+ }
+ Punt::HandlePushMessage(payload) => fxa
+ .handle_push_message(&payload)
+ .map(PuntResult::json_stringify),
+ Punt::PollDeviceCommands => fxa
+ .poll_device_commands(CommandFetchReason::Poll)
+ .map(PuntResult::json_stringify),
+ Punt::SendSingleTab(target_id, title, url) => fxa
+ .send_tab(&target_id, &title, &url)
+ .map(|_| PuntResult::Null),
+ Punt::SetDevicePushSubscription(push_subscription) => fxa
+ .set_push_subscription(&push_subscription)
+ .map(PuntResult::json_stringify),
+ Punt::InitializeDevice(name, device_type, capabilities) => fxa
+ .initialize_device(&name, device_type.clone(), &capabilities)
+ .map(|_| PuntResult::Null),
+ Punt::EnsureCapabilities(capabilities) => fxa
+ .ensure_capabilities(&capabilities)
+ .map(|_| PuntResult::Null),
+ }?)
+ }
+}
+
+fn to_capabilities(capabilities: &[nsCString]) -> error::Result<Vec<FxaDeviceCapability>> {
+ Ok(capabilities.iter().try_fold(
+ Vec::with_capacity(capabilities.len()),
+ |mut acc, capability| -> error::Result<_> {
+ let capability = str::from_utf8(&*capability)?;
+ acc.push(match capability {
+ "sendTab" => FxaDeviceCapability::SendTab,
+ _ => return Err(Error::Nsresult(nserror::NS_ERROR_INVALID_ARG)),
+ });
+ Ok(acc)
+ },
+ )?)
+}
+
+fn to_device_type(device_type: &nsACString) -> error::Result<FxaDeviceType> {
+ let device_type = str::from_utf8(&*device_type)?;
+ Ok(match device_type {
+ "desktop" => FxaDeviceType::Desktop,
+ "mobile" => FxaDeviceType::Mobile,
+ "tablet" => FxaDeviceType::Tablet,
+ "tv" => FxaDeviceType::TV,
+ "vr" => FxaDeviceType::VR,
+ _ => return Err(Error::Nsresult(nserror::NS_ERROR_INVALID_ARG)),
+ })
+}
+
+impl Task for PuntTask {
+ fn run(&self) {
+ *self.result.borrow_mut() = match mem::take(&mut *self.punt.borrow_mut()) {
+ Some(punt) => self.run_with_punt(punt),
+ // A task should never run on the background queue twice, but we
+ // return an error just in case.
+ None => Err(Error::AlreadyRan(self.name)),
+ };
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ let callback = self.callback.get().unwrap();
+ match mem::replace(
+ &mut *self.result.borrow_mut(),
+ Err(Error::AlreadyRan(self.name)),
+ ) {
+ Ok(result) => unsafe { callback.HandleSuccess(result.into_variant().coerce()) },
+ Err(err) => {
+ let mut message = nsCString::new();
+ write!(message, "{}", err).unwrap();
+ unsafe { callback.HandleError(err.into(), &*message) }
+ }
+ }
+ .to_result()
+ }
+}