Overview

Medium
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Sharing
DescriptionUse after free in Sharing
ComponentSharing
Sub-componentSTTS
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker547322272
Fix commit3c0d8469dea1 (chromium/src) +151/-17
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-08-16 (per vendor advisory)
Fix landed2026-08-17 (public git)
Disclosed2026-09-08
Patch gap22 days — fix public in source before the advisory

Background

Send Tab To Self (STTS)
a Chrome sync feature that pushes a URL “entry” from one signed-in device to another so it can be reopened, surfaced on iOS via an infobar.
`SendTabToSelfBrowserAgent`
the iOS per-browser object that observes the SendTabToSelfModel for remotely-added entries and shows an infobar on the active, visible WebState.
Pending entry
an STTS entry that arrived while the active tab was not yet visible, so its display is deferred until the tab is shown or another tab is activated.
`SendTabToSelfModel::RemoveEntryRemotely`
the model owns each SendTabToSelfEntry; a remote removal (or history clearing) destroys the entry object and then notifies observers.

Root Cause Analysis

When a remote STTS entry arrived while the active WebState was not visible, SendTabToSelfBrowserAgent cached a raw pointer to the model-owned SendTabToSelfEntry in pending_entry_ and waited for the tab to become visible. Because the model destroys an entry before notifying observers of its removal (as the fake’s reordered RemoveEntryRemotely now reproduces), any subsequent destruction of that entry — for example during history clearing or a remote removal — left pending_entry_ dangling. The removal-notification handler (DismissEntries, per the commit message) then dereferenced pending_entry_->GetGUID(), and the deferred display paths would likewise pass the freed pointer to DisplayInfoBar, both reading through freed heap memory.

The fix stores the entry’s stable GUID in std::optional<std::string> pending_entry_guid_ instead of a pointer, compares GUIDs by value in the removal handler, and re-resolves the live entry through model_->GetEntryByGUID(...) (null-checking the result) before any display, so a freed entry is never dereferenced.

Key insight
The agent held a raw pointer to an object owned and freed by the model, violating the ownership invariant that the entry may be destroyed before the deferred use; the fix removes the dangling reference entirely by keying on the immutable GUID string and re-looking-up the entry from its owner at the moment of use.

Attack Path

  1. Establish sync state The victim is signed in with Send Tab To Self enabled, so remotely-shared entries flow into the local SendTabToSelfModel and are observed by SendTabToSelfBrowserAgent.
  2. Deliver entry to a hidden tab A remote entry is added while the active WebState is not visible, so the agent stores a raw pending_entry_ pointer instead of displaying an infobar immediately.
  3. Free the entry The same entry is destroyed while still pending (remote removal via RemoveEntryRemotely, or entries cleared during history clearing), and the model frees the SendTabToSelfEntry before notifying observers, leaving pending_entry_ dangling.
  4. Trigger the dereference The removal handler DismissEntries reads pending_entry_->GetGUID() on the freed object, or, if that path is missed, showing/activating the tab passes the freed pending_entry_ to DisplayInfoBar, producing a heap use-after-free read.
  5. Escalate (inferred) With heap grooming to control the contents of the reallocated slot, the dangling read could be steered toward further memory disclosure or corruption; the patch itself only demonstrates the freed-memory read.

Impact Assessment

An attacker who can influence the victim’s STTS entries (a paired or attacker-controlled synced device, or crafted sync data) plus removal/visibility timing can trigger a use-after-free of a SendTabToSelfEntry in the iOS browser process. The patch demonstrates a freed-memory read (GetGUID() / infobar fields); any escalation to broader information disclosure or code execution is (inferred). The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6.

Changed Functions

FunctionChangeNotes
for
components/send_tab_to_self/fake_send_tab_to_self_model.cc
modified
if
ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.mm
modified
TEST_F
ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
modified
SendTabToSelfBrowserAgentToastDisabledTest
ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
modified

Files Changed

  • components/send_tab_to_self/fake_send_tab_to_self_model.cc
  • ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.h
  • components/send_tab_to_self/fake_send_tab_to_self_model.h
  • ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
  • ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.mm

Audit Directions

  • Raw pointers to sync/model-owned objects across a deferral
    Flag any observer or browser agent that caches a raw_ptr to a model-owned entity (STTS entries, bookmarks, history rows) to use later; prefer a stable key re-resolved from the owner at point of use.
  • Destroy-before-notify ordering
    Audit models whose removal path frees an object before firing OnEntriesRemovedRemotely-style notifications, since observers may still hold and dereference references to the just-freed object during the callback.
  • Fake/mock divergence from production ordering
    Where a fake reorders operations relative to the real implementation (here, erase vs. notify), tests can mask lifetime bugs; align fakes with real destruction ordering and add coverage for the removed-while-pending window.
From 3c0d8469dea16aaeb7bc9feb1d01cdaa5131945e Mon Sep 17 00:00:00 2001
From: Michael Tatarski <mtatarski@google.com>
Date: Mon, 17 Aug 2026 06:31:33 -0700
Subject: [PATCH] [STTS][iOS] Fix UAF by tracking pending entry via GUID

SendTabToSelfBrowserAgent previously held a raw pointer to a pending
SendTabToSelfEntry when a remote entry arrived while the active tab was
not visible. If entries were destroyed prior to notifying observers
(e.g. during history clearing), dereferencing the pointer in
DismissEntries caused a heap-use-after-free.

This change stores the GUID string instead of a raw pointer and resolves
the entry dynamically from the model when displaying the infobar.

Fixed: 547322272
Change-Id: I841a2469f40aa18308b18a50d55ec832bd4540bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8252063
Commit-Queue: Michael Tatarski <mtatarski@google.com>
Reviewed-by: Marc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1680497}
---
diff --git a/components/send_tab_to_self/fake_send_tab_to_self_model.cc b/components/send_tab_to_self/fake_send_tab_to_self_model.cc
index 564b63a..177456e 100644
--- a/components/send_tab_to_self/fake_send_tab_to_self_model.cc
+++ b/components/send_tab_to_self/fake_send_tab_to_self_model.cc
@@ -259,13 +259,13 @@
   return results;
 }
 
-void FakeSendTabToSelfModel::RemoveEntryRemotely(const std::string& guid) {
+void FakeSendTabToSelfModel::RemoveEntryRemotely(std::string guid) {
   auto it = entries_.find(guid);
   if (it != entries_.end()) {
+    entries_.erase(it);
     for (auto& observer : observers_) {
       observer.OnEntriesRemovedRemotely({guid});
     }
-    entries_.erase(it);
   }
 }
 
diff --git a/components/send_tab_to_self/fake_send_tab_to_self_model.h b/components/send_tab_to_self/fake_send_tab_to_self_model.h
index cc74669a..6094e7d 100644
--- a/components/send_tab_to_self/fake_send_tab_to_self_model.h
+++ b/components/send_tab_to_self/fake_send_tab_to_self_model.h
@@ -94,8 +94,9 @@
   std::vector<const SendTabToSelfEntry*> AddEntriesRemotely(
       std::vector<RemoteEntryParams> entries_params);
 
-  // Simulates an entry being removed from a remote device.
-  void RemoveEntryRemotely(const std::string& guid);
+  // Removes the entry corresponding to the `guid` from the local model and
+  // notify observers that the entry was removed remotely.
+  void RemoveEntryRemotely(std::string guid);
 
   const std::string& last_opened_guid() const { return last_opened_guid_; }
   const std::string& last_dismissed_guid() const {
diff --git a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.h b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.h
index 328cf5cb..de9b50b 100644
--- a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.h
+++ b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.h
@@ -155,8 +155,9 @@
   // Owned by the SendTabToSelfSyncService which should outlive this class
   raw_ptr<send_tab_to_self::SendTabToSelfModel> model_ = nullptr;
 
-  // The pending SendTabToSelf entry to display an InfoBar for.
-  raw_ptr<const send_tab_to_self::SendTabToSelfEntry> pending_entry_ = nullptr;
+  // The GUID of the pending SendTabToSelf entry to display an InfoBar for,
+  // if any.
+  std::optional<std::string> pending_entry_guid_;
 
   // The WebState that is being observed for activation, if any.
   raw_ptr<web::WebState> pending_web_state_ = nullptr;
diff --git a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.mm b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.mm
index cbbdcc57..a35217ed 100644
--- a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.mm
+++ b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.mm
@@ -287,7 +287,10 @@
 
     // Pick the most recently shared entry since only one infobar can be shown
     // at a time.
-    pending_entry_ = GetMostRecentlySharedEntry(new_entries);
+    const send_tab_to_self::SendTabToSelfEntry* entry =
+        GetMostRecentlySharedEntry(new_entries);
+    pending_entry_guid_ =
+        entry ? std::make_optional(entry->GetGUID()) : std::nullopt;
 
     return;
   }
@@ -309,8 +312,8 @@
     return;
   }
 
-  if (pending_entry_ &&
-      std::ranges::contains(guids, pending_entry_->GetGUID())) {
+  if (pending_entry_guid_ &&
+      std::ranges::contains(guids, *pending_entry_guid_)) {
     CleanUpObserversAndVariables();
   }
 
@@ -353,10 +356,16 @@
     return;
   }
 
-  if (pending_entry_) {
-    DisplayInfoBar(new_active, pending_entry_, /*opened_tab_count=*/1);
-    CleanUpObserversAndVariables();
+  if (!pending_entry_guid_) {
+    return;
   }
+
+  const send_tab_to_self::SendTabToSelfEntry* entry =
+      model_->GetEntryByGUID(*pending_entry_guid_);
+  if (entry) {
+    DisplayInfoBar(new_active, entry, /*opened_tab_count=*/1);
+  }
+  CleanUpObserversAndVariables();
 }
 
 #pragma mark - WebStateObserver
@@ -368,10 +377,14 @@
     return;
   }
 
-  DCHECK(pending_entry_);
+  DCHECK(pending_entry_guid_.has_value());
   DCHECK(pending_web_state_);
 
-  DisplayInfoBar(pending_web_state_, pending_entry_, /*opened_tab_count=*/1);
+  const send_tab_to_self::SendTabToSelfEntry* entry =
+      model_->GetEntryByGUID(*pending_entry_guid_);
+  if (entry) {
+    DisplayInfoBar(pending_web_state_, entry, /*opened_tab_count=*/1);
+  }
 
   CleanUpObserversAndVariables();
 }
@@ -410,7 +423,7 @@
 }
 
 void SendTabToSelfBrowserAgent::CleanUpObserversAndVariables() {
-  pending_entry_ = nullptr;
+  pending_entry_guid_.reset();
 
   web_state_observation_.Reset();
   pending_web_state_ = nullptr;
diff --git a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
index 6400e4a..b70f0c2 100644
--- a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
+++ b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
@@ -397,6 +397,126 @@
   EXPECT_EQ(0UL, infobar_manager->infobars().size());
 }
 
+// Tests that when an entry is added while the active WebState is not visible,
+// and then the entry is removed remotely (deallocated), showing the WebState
+// afterwards does not show an InfoBar or cause a use-after-free crash.
+TEST_F(SendTabToSelfBrowserAgentTest, TestRemoteRemovePendingNotVisibleTab) {
+  // Add a web state, active but not visible.
+  web::WebState* web_state =
+      AppendNewWebState(GURL("http://www.blank.com"),
+                        /*activate=*/true, /*is_visible=*/false);
+  InfoBarManagerImpl* infobar_manager =
+      InfoBarManagerImpl::FromWebState(web_state);
+  EXPECT_EQ(0UL, infobar_manager->infobars().size());
+
+  // Remote entry added while tab is not visible (so pending_entry_guid_ is
+  // set).
+  const SendTabToSelfEntry* entry = model_->AddEntryRemotely(
+      GURL("http://www.test.com/test-1"), "title", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(entry);
+  std::string guid = entry->GetGUID();
+
+  // No visible web state, so expect no infobar yet.
+  EXPECT_EQ(0UL, infobar_manager->infobars().size());
+
+  // Remove the entry remotely (which erases the entry and calls
+  // DismissEntries).
+  model_->RemoveEntryRemotely(guid);
+
+  // Show the web state.
+  web_state->WasShown();
+
+  // No infobar should be added since the pending entry was removed.
+  EXPECT_EQ(0UL, infobar_manager->infobars().size());
+}
+
+// Tests that removing an unrelated entry remotely removes its InfoBar but
+// preserves the pending entry for a not-yet-visible WebState.
+TEST_F(SendTabToSelfBrowserAgentTest,
+       TestRemoteRemoveUnrelatedEntryPreservesPending) {
+  // Add first web state, active and visible.
+  web::WebState* web_state1 = AppendNewWebState(GURL("http://www.blank.com"));
+  InfoBarManagerImpl* infobar_manager1 =
+      InfoBarManagerImpl::FromWebState(web_state1);
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+
+  // Add an entry for the visible web state.
+  const SendTabToSelfEntry* entry1 = model_->AddEntryRemotely(
+      GURL("http://www.test.com/first"), "title1", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(entry1);
+  std::string guid1 = entry1->GetGUID();
+  EXPECT_EQ(1UL, infobar_manager1->infobars().size());
+
+  // Add second web state, active but not visible.
+  web::WebState* web_state2 =
+      AppendNewWebState(GURL("http://www.blank.com"),
+                        /*activate=*/true, /*is_visible=*/false);
+  InfoBarManagerImpl* infobar_manager2 =
+      InfoBarManagerImpl::FromWebState(web_state2);
+  EXPECT_EQ(0UL, infobar_manager2->infobars().size());
+
+  // Add the pending entry for the non-visible web state.
+  const SendTabToSelfEntry* pending_entry = model_->AddEntryRemotely(
+      GURL("http://www.test.com/pending"), "title2", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(pending_entry);
+  std::string pending_guid = pending_entry->GetGUID();
+  EXPECT_EQ(0UL, infobar_manager2->infobars().size());
+
+  // Remove the first entry remotely.
+  model_->RemoveEntryRemotely(guid1);
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+
+  // Show the second web state.
+  web_state2->WasShown();
+
+  // An infobar for the pending entry should now be added to the second web
+  // state.
+  ASSERT_EQ(1UL, infobar_manager2->infobars().size());
+  infobars::InfoBar* infobar = infobar_manager2->infobars()[0];
+  auto* delegate =
+      static_cast<send_tab_to_self::IOSSendTabToSelfInfoBarDelegate*>(
+          infobar->delegate());
+  EXPECT_EQ(pending_guid, delegate->GetGUID());
+}
+
+// Tests that when an entry is added while the active WebState is not visible,
+// and then the entry is removed remotely (deallocated), switching to another
+// active WebState does not show an InfoBar or cause a use-after-free crash.
+TEST_F(SendTabToSelfBrowserAgentTest,
+       TestRemoteRemovePendingNotVisibleTabSwitchesActiveTab) {
+  // Add a web state, active but not visible.
+  web::WebState* web_state1 =
+      AppendNewWebState(GURL("http://www.blank.com"),
+                        /*activate=*/true, /*is_visible=*/false);
+  InfoBarManagerImpl* infobar_manager1 =
+      InfoBarManagerImpl::FromWebState(web_state1);
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+
+  // Remote entry added while tab 1 is not visible (so pending_entry_guid_ is
+  // set).
+  const SendTabToSelfEntry* entry = model_->AddEntryRemotely(
+      GURL("http://www.test.com/test-1"), "title", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(entry);
+  std::string guid = entry->GetGUID();
+
+  // Remove the entry remotely (which erases the entry and calls
+  // DismissEntries).
+  model_->RemoveEntryRemotely(guid);
+
+  // Add and activate a second web state.
+  web::WebState* web_state2 = AppendNewWebState(GURL("http://www.blank.com"));
+  InfoBarManagerImpl* infobar_manager2 =
+      InfoBarManagerImpl::FromWebState(web_state2);
+
+  // No infobar should be added to either web state since the entry was removed.
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+  EXPECT_EQ(0UL, infobar_manager2->infobars().size());
+}
+
 // Tests that SendTabToSelfLoadNavigationUserData is correctly attached or
 // detached when TabWillLoadUrl is triggered.
 TEST_F(SendTabToSelfBrowserAgentTest, TestTabWillLoadUrl) {
@@ -759,8 +879,7 @@
  public:
   SendTabToSelfBrowserAgentToastEnabledTest()
       : SendTabToSelfBrowserAgentTest(
-            {send_tab_to_self::kSendTabToSelfPostSendToast},
-            {}) {}
+            {send_tab_to_self::kSendTabToSelfPostSendToast}) {}
 };
 
 class SendTabToSelfBrowserAgentToastDisabledTest
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
index 6400e4a..b70f0c2 100644
--- a/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
+++ b/ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent_unittest.mm
@@ -397,6 +397,126 @@
   EXPECT_EQ(0UL, infobar_manager->infobars().size());
 }
 
+// Tests that when an entry is added while the active WebState is not visible,
+// and then the entry is removed remotely (deallocated), showing the WebState
+// afterwards does not show an InfoBar or cause a use-after-free crash.
+TEST_F(SendTabToSelfBrowserAgentTest, TestRemoteRemovePendingNotVisibleTab) {
+  // Add a web state, active but not visible.
+  web::WebState* web_state =
+      AppendNewWebState(GURL("http://www.blank.com"),
+                        /*activate=*/true, /*is_visible=*/false);
+  InfoBarManagerImpl* infobar_manager =
+      InfoBarManagerImpl::FromWebState(web_state);
+  EXPECT_EQ(0UL, infobar_manager->infobars().size());
+
+  // Remote entry added while tab is not visible (so pending_entry_guid_ is
+  // set).
+  const SendTabToSelfEntry* entry = model_->AddEntryRemotely(
+      GURL("http://www.test.com/test-1"), "title", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(entry);
+  std::string guid = entry->GetGUID();
+
+  // No visible web state, so expect no infobar yet.
+  EXPECT_EQ(0UL, infobar_manager->infobars().size());
+
+  // Remove the entry remotely (which erases the entry and calls
+  // DismissEntries).
+  model_->RemoveEntryRemotely(guid);
+
+  // Show the web state.
+  web_state->WasShown();
+
+  // No infobar should be added since the pending entry was removed.
+  EXPECT_EQ(0UL, infobar_manager->infobars().size());
+}
+
+// Tests that removing an unrelated entry remotely removes its InfoBar but
+// preserves the pending entry for a not-yet-visible WebState.
+TEST_F(SendTabToSelfBrowserAgentTest,
+       TestRemoteRemoveUnrelatedEntryPreservesPending) {
+  // Add first web state, active and visible.
+  web::WebState* web_state1 = AppendNewWebState(GURL("http://www.blank.com"));
+  InfoBarManagerImpl* infobar_manager1 =
+      InfoBarManagerImpl::FromWebState(web_state1);
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+
+  // Add an entry for the visible web state.
+  const SendTabToSelfEntry* entry1 = model_->AddEntryRemotely(
+      GURL("http://www.test.com/first"), "title1", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(entry1);
+  std::string guid1 = entry1->GetGUID();
+  EXPECT_EQ(1UL, infobar_manager1->infobars().size());
+
+  // Add second web state, active but not visible.
+  web::WebState* web_state2 =
+      AppendNewWebState(GURL("http://www.blank.com"),
+                        /*activate=*/true, /*is_visible=*/false);
+  InfoBarManagerImpl* infobar_manager2 =
+      InfoBarManagerImpl::FromWebState(web_state2);
+  EXPECT_EQ(0UL, infobar_manager2->infobars().size());
+
+  // Add the pending entry for the non-visible web state.
+  const SendTabToSelfEntry* pending_entry = model_->AddEntryRemotely(
+      GURL("http://www.test.com/pending"), "title2", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(pending_entry);
+  std::string pending_guid = pending_entry->GetGUID();
+  EXPECT_EQ(0UL, infobar_manager2->infobars().size());
+
+  // Remove the first entry remotely.
+  model_->RemoveEntryRemotely(guid1);
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+
+  // Show the second web state.
+  web_state2->WasShown();
+
+  // An infobar for the pending entry should now be added to the second web
+  // state.
+  ASSERT_EQ(1UL, infobar_manager2->infobars().size());
+  infobars::InfoBar* infobar = infobar_manager2->infobars()[0];
+  auto* delegate =
+      static_cast<send_tab_to_self::IOSSendTabToSelfInfoBarDelegate*>(
+          infobar->delegate());
+  EXPECT_EQ(pending_guid, delegate->GetGUID());
+}
+
+// Tests that when an entry is added while the active WebState is not visible,
+// and then the entry is removed remotely (deallocated), switching to another
+// active WebState does not show an InfoBar or cause a use-after-free crash.
+TEST_F(SendTabToSelfBrowserAgentTest,
+       TestRemoteRemovePendingNotVisibleTabSwitchesActiveTab) {
+  // Add a web state, active but not visible.
+  web::WebState* web_state1 =
+      AppendNewWebState(GURL("http://www.blank.com"),
+                        /*activate=*/true, /*is_visible=*/false);
+  InfoBarManagerImpl* infobar_manager1 =
+      InfoBarManagerImpl::FromWebState(web_state1);
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+
+  // Remote entry added while tab 1 is not visible (so pending_entry_guid_ is
+  // set).
+  const SendTabToSelfEntry* entry = model_->AddEntryRemotely(
+      GURL("http://www.test.com/test-1"), "title", kDeviceID,
+      send_tab_to_self::PageContext(), send_tab_to_self::NavigationHistory());
+  ASSERT_TRUE(entry);
+  std::string guid = entry->GetGUID();
+
+  // Remove the entry remotely (which erases the entry and calls
+  // DismissEntries).
+  model_->RemoveEntryRemotely(guid);
+
+  // Add and activate a second web state.
+  web::WebState* web_state2 = AppendNewWebState(GURL("http://www.blank.com"));
+  InfoBarManagerImpl* infobar_manager2 =
+      InfoBarManagerImpl::FromWebState(web_state2);
+
+  // No infobar should be added to either web state since the entry was removed.
+  EXPECT_EQ(0UL, infobar_manager1->infobars().size());
+  EXPECT_EQ(0UL, infobar_manager2->infobars().size());
+}
+
 // Tests that SendTabToSelfLoadNavigationUserData is correctly attached or
 // detached when TabWillLoadUrl is triggered.
 TEST_F(SendTabToSelfBrowserAgentTest, TestTabWillLoadUrl) {
@@ -759,8 +879,7 @@
  public:
   SendTabToSelfBrowserAgentToastEnabledTest()
       : SendTabToSelfBrowserAgentTest(
-            {send_tab_to_self::kSendTabToSelfPostSendToast},
-            {}) {}
+            {send_tab_to_self::kSendTabToSelfPostSendToast}) {}
 };
 
 class SendTabToSelfBrowserAgentToastDisabledTest
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.