Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .buildconfig-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ projects:
- name: autofill
type: aar
description: Addresses and Credit Cards autofill.
containers:
path: components/containers/android
artifactId: containers
publications:
- name: containers
type: aar
description: Storage for Firefox containers.
crashtest:
path: components/crashtest/android
artifactId: crashtest
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@

## ✨ What's Changed ✨

### Containers

- Created a new component, `containers`, holding the list of Firefox containers and the format they are stored in.
- The component also stores the containers an enterprise policy owns: `create_for_policy()`, `policy_identities()`, `policy_identity()` and `remove_policy_identity()`. Which URLs load in one of them stays with the embedder.

### Autofill

- `update_address()` now sets `time_last_modified` to the time of the update, matching `update_credit_card()` and `update_passport()`.
Expand Down
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"components/as-ohttp-client",
"components/autofill",
"components/breach-alerts",
"components/containers",
"components/context_id",
"components/crashtest",
"components/example",
Expand Down Expand Up @@ -109,6 +110,7 @@ default-members = [
"components/as-ohttp-client",
"components/autofill",
"components/breach-alerts",
"components/containers",
"components/context_id",
"components/crashtest",
"components/fxa-client",
Expand Down
15 changes: 15 additions & 0 deletions components/containers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "containers"
description = "Storage for Firefox containers"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"

[dependencies]
error-support = { path = "../support/error" }
idna = "1"
parking_lot = "0.12"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
uniffi = { version = "0.32" }
28 changes: 28 additions & 0 deletions components/containers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Containers

Storage for Firefox containers.

The component never touches the filesystem. `ContainersStore` hands the
serialized document to a callback, and the embedder decides where it goes and
how durably.

It owns the list, not the behaviour. Everything that gives a container its
meaning stays outside:

- the origin attributes that isolate its cookies and storage
- clearing that storage when a container is removed
- resolving the localized labels of the shipped containers
- closing the tabs that belong to one
- deciding which site loads in which container, enterprise policy included

## Tests

Tests are run with

```shell
cargo test -p containers
```

## Bugs

We use Bugzilla to track bugs and feature work. You can use [this link](https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Containers) to file bugs in the `Firefox :: Containers` bug component.
10 changes: 10 additions & 0 deletions components/containers/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apply from: "$appServicesRootDir/build-scripts/component-common.gradle"
apply from: "$publishDir/publish.gradle"

android {
namespace 'org.mozilla.appservices.containers'
}

ext.configureUniFFIBindgen("containers")
ext.dependsOnTheMegazord()
ext.configurePublish(appServicesGroupId, project.name, project.ext.description)
22 changes: 22 additions & 0 deletions components/containers/android/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

6 changes: 6 additions & 0 deletions components/containers/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
44 changes: 44 additions & 0 deletions components/containers/src/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* 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::data::Identity;
use crate::definitions::{self, ContainerColor, ContainerIcon, ContainerLabel};

/// A container as the embedder sees it.
#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
pub struct Container {
pub user_context_id: u32,
pub is_public: bool,
pub icon: Option<ContainerIcon>,
pub color: Option<ContainerColor>,
pub label: ContainerLabel,
pub policy_id: Option<String>,
}

impl Container {
/// `default_label` is the label of the default identity that owns this
/// container's id, if any. It labels a default container the user has not
/// renamed, since the label of a default is not stored.
pub(crate) fn from_identity(
identity: &Identity,
default_label: Option<&ContainerLabel>,
) -> Self {
Self {
user_context_id: identity.user_context_id,
is_public: identity.public,
icon: definitions::icon_from_name(&identity.icon),
color: definitions::color_from_name(&identity.color),
policy_id: identity
.policy
.then(|| identity.policy_id.clone())
.flatten(),
label: match &identity.name {
Some(name) if !name.is_empty() => ContainerLabel::Name { name: name.clone() },
_ => default_label.cloned().unwrap_or(ContainerLabel::Name {
name: String::new(),
}),
},
}
}
}
79 changes: 79 additions & 0 deletions components/containers/src/data.rs
Original file line number Diff line number Diff line change
@@ -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 std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

pub(crate) const LATEST_VERSION: u32 = 8;

/// Reserved for the IndexedDB backend of the extension storage.local API. Never
/// reassign it: extensions would lose access to data stored under it.
pub(crate) const MAX_USER_CONTEXT_ID: u32 = u32::MAX;

/// Fields that no version of the format knows about are round-tripped verbatim
/// through `extra`, so that a document written by a newer Firefox keeps its
/// data when an older one rewrites it.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Identity {
#[serde(default)]
pub user_context_id: u32,
#[serde(default)]
pub public: bool,
#[serde(default)]
pub icon: String,
#[serde(default)]
pub color: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub policy: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub policy_id: Option<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContainersData {
#[serde(default)]
pub version: u32,
#[serde(default)]
pub last_user_context_id: u32,
#[serde(default)]
pub identities: Vec<Identity>,
#[serde(default)]
pub site_associations: BTreeMap<String, u32>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}

impl ContainersData {
pub(crate) fn public_identities(&self) -> impl Iterator<Item = &Identity> {
self.identities.iter().filter(|identity| identity.public)
}

pub(crate) fn private_identities(&self) -> impl Iterator<Item = &Identity> {
self.identities.iter().filter(|identity| !identity.public)
}

pub(crate) fn policy_identities(&self) -> impl Iterator<Item = &Identity> {
self.identities.iter().filter(|identity| identity.policy)
}

pub(crate) fn find_private_by_name(&self, name: &str) -> Option<&Identity> {
self.identities.iter().find(|identity| {
!identity.public && !identity.policy && identity.name.as_deref() == Some(name)
})
}

pub(crate) fn find_policy_by_id(&self, policy_id: &str) -> Option<&Identity> {
self.identities
.iter()
.find(|identity| identity.policy && identity.policy_id.as_deref() == Some(policy_id))
}
}
Loading
Loading