PHANTOM
🇮🇳 IN
Skip to content

Support contained tabs session restore and sync.#33313

Open
goodov wants to merge 2 commits intomasterfrom
containers-session-restore-sync
Open

Support contained tabs session restore and sync.#33313
goodov wants to merge 2 commits intomasterfrom
containers-session-restore-sync

Conversation

@goodov
Copy link
Member

@goodov goodov commented Jan 20, 2026

Implement session persistence for Containers.

Containers use StoragePartitionConfig for data isolation, but the config information wasn't persisted across sessions, causing tabs to lose isolation on restore.

This change encodes StoragePartitionConfig into URLs during serialization using a virtual prefix: containers+<uuid>:<actual_url>.

On serialization, the prefix is added to both SerializedNavigationEntry and PageState. On deserialization, StoragePartitionConfig is restored and the prefix removed from PageState (when feature is enabled).

When the feature is disabled, the prefix remains in PageState. Blink attempts to navigate to the unhandleable containers+uuid: scheme, resulting in a blank page rather than loading in the wrong partition.

This works with existing session/sync infrastructure and enables container tabs to survive browser restart, crash recovery, "reopen closed tab", and cross-device sync while maintaining storage isolation.

Please refer to the documentation https://github.com/brave/brave-core/blob/containers-session-restore-sync/docs/containers/session_persistence.md

Resolves brave/brave-browser#47306
Resolves brave/brave-browser#46353

@goodov goodov force-pushed the containers-session-restore-sync branch from 2242590 to 69e5237 Compare January 21, 2026 11:59
@goodov goodov force-pushed the containers-tab-ctx-menu-integration branch 2 times, most recently from fb2f727 to 6247a7a Compare January 27, 2026 13:41
Base automatically changed from containers-tab-ctx-menu-integration to master January 27, 2026 14:48
@goodov goodov force-pushed the containers-session-restore-sync branch from 69e5237 to 6af0f4f Compare January 28, 2026 08:12
@github-actions
Copy link
Contributor

github-actions bot commented Jan 28, 2026

📋 Code Owners Summary

28 file(s) changed, 18 with assigned owners

3 team(s) affected: @brave/chromium-src-reviewers, @brave/deps-reviewers, @brave/patch-reviewers


Owners and Their Files

@brave/chromium-src-reviewers — 12 file(s)

... and 7 more files

@brave/patch-reviewers — 5 file(s)

@brave/deps-reviewers — 1 file(s)

@github-actions
Copy link
Contributor

Chromium major version is behind target branch (144.0.7559.110 vs 145.0.7632.31). Please rebase.

@github-actions github-actions bot added the chromium-version-mismatch The Chromium version on the PR branch does not match the version on the target branch label Jan 29, 2026
@goodov goodov force-pushed the containers-session-restore-sync branch from 6af0f4f to 2571210 Compare February 3, 2026 16:17
@github-actions github-actions bot removed the chromium-version-mismatch The Chromium version on the PR branch does not match the version on the target branch label Feb 3, 2026
@goodov goodov force-pushed the containers-session-restore-sync branch 3 times, most recently from 9de458a to 48c57a0 Compare February 9, 2026 13:13
@goodov goodov force-pushed the containers-session-restore-sync branch 4 times, most recently from c74e22c to 5b4e565 Compare February 16, 2026 12:21
@goodov goodov marked this pull request as ready for review February 16, 2026 13:29
@goodov goodov requested review from a team as code owners February 16, 2026 13:29
@goodov
Copy link
Member Author

goodov commented Feb 17, 2026

For reviewers: please refer to a short doc that explains what's going on here in more details https://github.com/brave/brave-core/blob/containers-session-restore-sync/docs/containers/session_persistence.md

@simonhong
Copy link
Member

Sorry for late - I'll review tomorrow.

Copy link
Member

@simonhong simonhong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ with some trivial question. 👍🏼

@goodov goodov force-pushed the containers-session-restore-sync branch from 5b4e565 to 053645b Compare February 20, 2026 09:44
@github-actions
Copy link
Contributor

[puLL-Merge] - brave/brave-core@33313

Description

This PR implements session persistence for Brave Containers, enabling container tabs to survive browser restarts, "Reopen closed tab" operations, and cross-device Sync. The core approach encodes the container's StoragePartitionConfig (partition_domain + partition_name) into a virtual URL scheme prefix (e.g., containers+<uuid>:https://example.com) during serialization. On deserialization, the prefix is detected, the original URL is restored, and the correct StoragePartitionConfig is applied.

The encoding strategy ensures that if Containers is disabled (or on an older browser), the encoded URL is unhandleable, preventing accidental mixing of container storage with default storage.

Possible Issues

  1. PageState prefix manipulation fragility: The PrefixTopURL and RemoveTopURLPrefix methods on blink::PageState manipulate a serialized blob by string offset. If Blink's PageState serialization format changes, this could silently corrupt page state data. The diff for these files is omitted but they are critical.

  2. kContainersStoragePartitionDomain renamed from "containers-default" to "containers": This is a breaking change for any existing container storage partitions created before this PR. Existing container data stored under "containers-default" will become inaccessible after this change, as the domain no longer matches. There's no migration path visible.

  3. Validation allows - at start/end of partition name: IsValidStoragePartitionKeyComponent allows - anywhere (including leading/trailing), but the unit tests show "-test_container" as false only because of the underscore—"-test-container" would pass. This could lead to ambiguous URL parsing if partition names start/end with -.

  4. CrossSiteNavigationPersistence test assumes tab restore order: The CrossSiteNavigationPersistence test's second half expects GetActiveWebContents() to return the previously-active container tab after restart, with no explicit navigation. This depends on session restore behavior that may be fragile across Chromium updates.

  5. MixedTabsPersistence test assumes exact tab ordering: The test checks tabs by index (i == 0, i == 1, etc.) after session restore. Tab ordering during session restore may not be guaranteed in all scenarios.

  6. Copyright year inconsistency: Several new files use 2026 as the copyright year (e.g., session_utils.cc, session_utils.h, session_utils_unittest.cc, synced_session_unittest.cc), which appears to be a typo.

Security Hotspots

  1. URL prefix parsing in RestoreStoragePartitionKeyFromUrl: The function splits the URL scheme on + and uses the result to construct a StoragePartitionConfig. A maliciously crafted URL scheme could potentially trick the parser into creating an unintended storage partition, though the IsContainersStoragePartitionKey check provides some defense. The validation only checks for alphanumeric and - characters, but there's no UUID format validation on the partition name.

  2. PageState blob manipulation: Prefixing/removing bytes from serialized PageState data without full structural understanding could potentially lead to page state corruption that a renderer might interpret unexpectedly. The actual implementation is in omitted files (page_state.cc/.h).

  3. set_open_about_blank_on_browser_launch(false) in test setup: This change affects session restore behavior in tests. While benign for testing, it's worth noting this changes the test harness behavior for all tests in the class.

Changes

Changes

browser/containers/containers_browsertest.cc

  • Added comprehensive browser tests: storage persistence across sessions (PRE_ pattern), service worker persistence, hot restore of closed tabs, cross-site navigation persistence, navigation history persistence, mixed container/non-container tab persistence, and behavior when Containers feature is disabled after restore.
  • Changed kContainersStoragePartitionDomain usage and introduced kTestContainerId constant.
  • Added storage partition config verification to link navigation test.
  • Added set_open_about_blank_on_browser_launch(false) to support session restore tests.

chromium_src/chrome/browser/ui/browser_tabrestore.cc

  • Override GetSiteInstanceForNewTab to pass StoragePartitionConfig extracted from serialized navigation entries during tab restore.

chromium_src/components/sessions/content/content_serialized_navigation_builder.cc/.h

  • Override FromNavigationEntry to inject virtual_url_prefix and storage_partition_key into SerializedNavigationEntry during session save.
  • Added patch hook BRAVE_CONTENT_SERIALIZED_NAVIGATION_BUILDER_TO_NAVIGATION_ENTRY to copy storage partition key into NavigationEntry during restore.

chromium_src/components/sessions/content/content_serialized_navigation_driver.cc/.h

  • Override GetSanitizedPageStateForPickle to prefix PageState URLs with container encoding.
  • Override Sanitize to detect and parse container-encoded URLs during session restore, extracting partition keys and restoring original URLs.

chromium_src/components/sessions/core/serialized_navigation_entry.h

  • Extends SerializedNavigationEntry with storage_partition_key_ and virtual_url_prefix_ runtime fields via macro patching.

chromium_src/content/browser/renderer_host/navigation_entry_impl.cc/.h and chromium_src/content/public/browser/navigation_entry.h

  • Adds SetStoragePartitionKeyToRestore/GetStoragePartitionKeyToRestore virtual methods to NavigationEntry and implementation in NavigationEntryImpl.

components/containers/content/browser/session_utils.cc/.h

  • New utility functions GetUrlPrefixForSessionPersistence and RestoreStoragePartitionKeyFromUrl for encoding/decoding container info in URLs.

components/containers/content/browser/storage_partition_utils.cc/.h

  • Renamed domain from "containers-default" to "containers".
  • Added IsContainersStoragePartitionKey and IsValidStoragePartitionKeyComponent with stricter validation (alphanumeric + - only).

Patches

  • chrome_serialized_navigation_driver.cc.patch: Calls parent Sanitize in Chrome's driver.
  • serialized_navigation_entry.cc.patch: Prepends virtual_url_prefix() when writing to pickle.
  • synced_session.cc.patch: Prepends virtual_url_prefix() in sync data serialization.
  • BUILD.gn.patch: Adds brave container deps to sessions component.

docs/containers/session_persistence.md

  • Comprehensive documentation of the serialization/deserialization flow.
sequenceDiagram
    participant Tab as Container Tab
    participant NE as NavigationEntry
    participant Builder as ContentSerializedNavigationBuilder
    participant SNE as SerializedNavigationEntry
    participant Driver as ContentSerializedNavigationDriver
    participant Disk as Disk/Sync

    Note over Tab,Disk: Serialization (Save Session)
    Tab->>NE: GetStoragePartitionKeyToRestore()
    NE-->>Builder: {containers, uuid}
    Builder->>SNE: set_virtual_url_prefix("containers+uuid:")
    Builder->>SNE: set_storage_partition_key({containers, uuid})
    SNE->>Driver: GetSanitizedPageStateForPickle()
    Driver->>Driver: PrefixTopURL("containers+uuid:")
    SNE->>Disk: WriteToPickle(prefix + url)
    SNE->>Disk: Sync(prefix + url)

    Note over Tab,Disk: Deserialization (Restore Session)
    Disk->>SNE: ReadFromPickle("containers+uuid:https://...")
    SNE->>Driver: Sanitize()
    Driver->>Driver: RestoreStoragePartitionKeyFromUrl()
    Driver->>SNE: set_virtual_url("https://...")
    Driver->>SNE: set_storage_partition_key({containers, uuid})
    Driver->>SNE: RemoveTopURLPrefix from PageState
    SNE->>Builder: ToNavigationEntry()
    Builder->>NE: SetStoragePartitionKeyToRestore({containers, uuid})
    NE->>Tab: Create SiteInstance with StoragePartitionConfig
Loading

Copy link
Member

@fmarier fmarier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read through the design doc and skimmed through the changes. The approach sounds right to me.

One minor thing I'd suggest testing is restoring/syncing URLs that already have a virtual scheme (e.g. view-source:https://example.com) just to make sure the view-source: doesn't get lost in the serialization/deserialization process.

Copy link
Collaborator

@netzenbot netzenbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review via brave-core-bot

#include "content/public/browser/browser_context.h"

#if BUILDFLAG(ENABLE_CONTAINERS)
#include "brave/components/containers/content/browser/storage_partition_utils.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This chromium_src file directly includes brave/components/containers/content/browser/storage_partition_utils.h and brave/components/containers/core/common/features.h. The same pattern appears in chromium_src/components/sessions/content/content_serialized_navigation_builder.cc and content_serialized_navigation_driver.cc. Per policy, chromium_src should not have GN dependencies on //brave/components/ targets. Consider using forward declarations with link-time resolution via sources.gni instead. best practice

// container prefix, extracts the storage partition key (partition_domain,
// partition_name) and the original URL (https://example.com).
COMPONENT_EXPORT(CONTAINERS_CONTENT_BROWSER)
std::optional<GURL> RestoreStoragePartitionKeyFromUrl(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestoreStoragePartitionKeyFromUrl uses out-parameters (storage_partition_key and url_prefix_length) alongside an optional<GURL> return. Consider returning a struct containing all three values instead, which eliminates the mutable reference parameters and makes the API safer and easier to use. best practice

#define RemoveScrollOffset() \
RemoveScrollOffset() const; \
PageState PrefixTopURL(const std::string& prefix) const; \
PageState RemoveTopURLPrefix(const size_t& prefix_length)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoveTopURLPrefix(const size_t& prefix_length) passes a primitive type by const reference. Pass size_t by value instead. best practice

// Verify data is set
content::EvalJsResult cookie_result =
content::EvalJs(web_contents, GetCookiesJS());
EXPECT_TRUE(cookie_result.ExtractString().find(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: EXPECT_TRUE(cookie_result.ExtractString().find(...) != std::string::npos) gives no diagnostic context on failure — just "expected true, got false". This pattern appears ~15 times in this file. Use EXPECT_THAT(cookies, ::testing::HasSubstr("persistent_cookie=persistent_value")) instead, which automatically includes the actual value in the failure output. best practice

});
}

std::optional<GURL> RestoreStoragePartitionKeyFromUrl(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider making return value [[nodiscard]]. The caller would always want to be checking the value


bool IsValidStoragePartitionKeyComponent(std::string_view component) {
return !component.empty() &&
std::ranges::all_of(component, [](const char& c) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partition key is realistically GUID/UUIDs. But should it be allowing plain text in here too? and no minimum length?

ex: containers+lol-lol-lol:http://example.com or containers+ :http://example.com would be OK?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to explicitly restrict things here to guid. We only care about restriction to have it serialized correctly (the emptiness check and the valid chars check). Other than that we may use some other ID syntax (prefixes for ex.) for ephemeral containers or something like that.

Maybe we can add a reasonable minimal length requirement, that makes sense I think.

? frame_tree_->frame_entry->site_instance()
: nullptr) {
const auto& storage_partition_config =
site_instance->GetStoragePartitionConfig();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the original value for a storage partition config come from?

Let's say we open a new tab in a container. Where does that UUID get generated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the guid is generated when you create a new container via settings page. see ContainersSettingsHandler::AddContainer.

@github-actions
Copy link
Contributor

Chromium major version is behind target branch (145.0.7632.109 vs 146.0.7680.32). Please rebase.

@github-actions github-actions bot added the chromium-version-mismatch The Chromium version on the PR branch does not match the version on the target branch label Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chromium-version-mismatch The Chromium version on the PR branch does not match the version on the target branch puLL-Merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Containers] Support containers in tab/session restore [Containers] Support containers in sync service

5 participants