CVE-2026-78961
Overview
Background
- User Activation v2
- browser-tracked “sticky” and “transient” activation state per frame, set by genuine user input and required by gesture-gated web APIs; it propagates to ancestors, and otherwise only to same-origin frames (“Same-origin Visibility”).
- Document Picture-in-Picture (PiP)
- an always-on-top window whose
WebContentsis opened by a page and linked to its opener frame tree, exposed viaGetDocumentPictureInPictureOpenerFrameTree(). - `kDocumentPictureInPictureUserActivation`
- a feature flag under which activation in a PiP window is also propagated to the opener frame tree (and vice versa); the regression test also enables
media::kDocumentPictureInPictureNavigationto navigate the PiP window cross-origin. - `VerifyCreateNewWindowParams`
- browser-side validation of the renderer-supplied
mojom::CreateNewWindowParams, which kills the renderer viabad_message::ReceivedBadMessageon invalid input.
Root Cause Analysis
In FrameTreeNode::NotifyUserActivation (named in the commit message), after activating the frame and its ancestors, the removed code unconditionally activated the root of the PiP opener frame tree (pip_opener->root()->current_frame_host()) whenever kDocumentPictureInPictureUserActivation was enabled and the tree was a PiP tree, without comparing origins. Every other cross-tree/sibling propagation in this function is restricted to frames whose GetLastCommittedOrigin() IsSameOriginWith(current_origin), so this path violated the “Same-origin Visibility” invariant: activation in a cross-origin PiP document gave the opener’s main frame sticky and transient activation.
The fix moves the PiP-opener block after current_origin is computed and only activates opener_frame_host when its origin is same-origin with the activated frame; the regression test shows a b.com PiP document no longer activates an a.com opener, while an a.com PiP document still does. Separately, the fix tightens VerifyCreateNewWindowParams to reject params.pip_options unless params.disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE, terminating the renderer with RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS, so a compromised renderer cannot forge a PiP relationship on an ordinary window.
NotifyUserActivation lacked the same-origin check used by every other non-ancestor propagation path, letting a cross-origin PiP frame grant activation to the opener’s main frame; the fix gates it on IsSameOriginWith(current_origin) and adds IPC validation so pip_options cannot accompany a non-PiP disposition.Attack Path
- PiP window with cross-origin content
A page’s PiP window ends up showing a document cross-origin to the opener’s main frame; the test builds this with a b.com subframe as PiP opener and a navigation of the PiP window to b.com (requires
kDocumentPictureInPictureUserActivation, and the test also enableskDocumentPictureInPictureNavigation). - User interacts with the PiP document
A genuine user activation in the cross-origin PiP frame reaches
FrameTreeNode::NotifyUserActivationin the browser. - Activation leaks to opener
Pre-fix, the PiP-opener branch called
DidReceiveUserActivationandActivateUserActivationon the opener tree’s root regardless of origin, giving the cross-origin opener main frame sticky and transient activation. - Forged PiP relationship (compromised renderer)
Pre-fix, a compromised renderer could also send
CreateNewWindowParamscarryingpip_optionswith a non-PiP disposition to set up a PiP-style opener relationship for an ordinary window, now rejected as a bad message (consequences beyond that are (inferred)). - Use of borrowed activation The activated cross-origin frame could invoke activation-gated APIs (e.g. popups or fullscreen) without the user having interacted with it (inferred).
Impact Assessment
pip_options forging variant additionally requires a compromised renderer. Concrete abuse of the borrowed activation is (inferred); the advisory states only “Incorrect authorization in Core” and reports no in-the-wild exploitation.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/frame_tree_node.cc |
modified | |
PictureInPictureFrameTreeBrowserTestcontent/browser/renderer_host/frame_tree_browsertest.cc |
modified | |
PictureInPictureFrameTreeBrowserTestcontent/browser/renderer_host/frame_tree_browsertest.cc |
modified | |
BrowserContextGroupSwapFrameTreeBrowserTestcontent/browser/renderer_host/frame_tree_browsertest.cc |
modified |
Files Changed
tools/metrics/histograms/metadata/stability/enums.xmlcontent/browser/bad_message.hcontent/browser/renderer_host/frame_tree_browsertest.cccontent/browser/renderer_host/ipc_utils.cccontent/browser/renderer_host/frame_tree_node.cccontent/browser/renderer_host/ipc_utils.h
Audit Directions
- Cross-tree activation propagationreview every place user activation crosses
WebContents/frame-tree boundaries (PiP in both directions, fenced frames, portals/prerender, guest views, popups/openers) for missingIsSameOriginWithgating matching “Same-origin Visibility”. - Renderer-supplied relationship flagsaudit
CreateNewWindowParamsand similar IPCs for optional fields (options structs, feature bits) that are honoured without cross-checking againstdispositionor other fields that should constrain them. - Feature-flagged security pathscheck code under flags like
kDocumentPictureInPictureUserActivationandkDocumentPictureInPictureNavigationfor checks present on the main path but missing on the flagged branch, especially after the flag lets content navigate to a new origin.
Patch
From bb53bffbfeb5f5f6fc898bad7bc68f52a4392de8 Mon Sep 17 00:00:00 2001
From: Tzarial <zork@google.com>
Date: Tue, 14 Jul 2026 08:25:38 -0700
Subject: [PATCH] [agy][content] Gate PiP user activation on origin
FrameTreeNode::NotifyUserActivation() propagates user activation from a
document picture-in-picture window to the root of its opener frame tree.
Unlike other paths, this path did not require the activated frame to be
same-origin with the opener's main frame.
Gate this propagation on IsSameOriginWith(), matching other paths.
Additionally, validate in VerifyCreateNewWindowParams that pip_options
are only allowed when disposition is NEW_PICTURE_IN_PICTURE. This
prevents compromised renderers from forging PiP relationships.
Fixed: 497269030
Test: PictureInPictureFrameTreeBrowserTest.*
Change-Id: I7cb7dc07e8b57335f11597ed7c70878a2fcf573f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086453
Reviewed-by: Caitlin Fischer <caitlinfischer@google.com>
Commit-Queue: Tzarial <zork@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1661875}
---
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 0102faa3..c943914 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -383,6 +383,7 @@
RFH_AUTHENTICATOR_PDF_PROCESS_BLOCKED = 355,
BIBI_BIND_GAMEPAD_MONITOR_BLOCKED_BY_PERMISSIONS_POLICY = 356,
BIBI_BIND_GAMEPAD_HAPTICS_MANAGER_BLOCKED_BY_PERMISSIONS_POLICY = 357,
+ RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS = 358,
// Please add new elements here. The naming convention is abbreviated class
// name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/renderer_host/frame_tree_node.cc b/content/browser/renderer_host/frame_tree_node.cc
index 5dba052..c07dca3 100644
--- a/content/browser/renderer_host/frame_tree_node.cc
+++ b/content/browser/renderer_host/frame_tree_node.cc
@@ -796,33 +796,23 @@
// User Activation V2 requires activating all ancestor frames in addition to
// the current frame. See
// https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation.
+ // This propagation is spec-mandated to cross origin boundaries (e.g., from
+ // a cross-origin subframe to its parent), so no same-origin check is
+ // performed here.
for (RenderFrameHostImpl* rfh = current_frame_host(); rfh;
rfh = rfh->GetParent()) {
rfh->DidReceiveUserActivation();
rfh->ActivateUserActivation(notification_type, sticky_only);
}
- // If we're in a picture-in-picture frame tree, then also activate the opener
- // frame of the picture-in-picture root.
- FrameTree* pip_opener =
- frame_tree().delegate()->GetDocumentPictureInPictureOpenerFrameTree();
- if (base::FeatureList::IsEnabled(
- blink::features::kDocumentPictureInPictureUserActivation) &&
- pip_opener) {
- RenderFrameHostImpl* opener_frame_host =
- pip_opener->root()->current_frame_host();
-
- opener_frame_host->DidReceiveUserActivation();
- opener_frame_host->ActivateUserActivation(notification_type, sticky_only);
- }
-
- current_frame_host()->browsing_context_state()->set_has_active_user_gesture(
- true);
-
// See the "Same-origin Visibility" section in |UserActivationState| class
// doc.
const url::Origin& current_origin =
this->current_frame_host()->GetLastCommittedOrigin();
+
+ current_frame_host()->browsing_context_state()->set_has_active_user_gesture(
+ true);
+
for (FrameTreeNode* node : frame_tree().Nodes()) {
if (node->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
current_origin)) {
@@ -833,6 +823,22 @@
if (base::FeatureList::IsEnabled(
blink::features::kDocumentPictureInPictureUserActivation)) {
+ // If we're in a picture-in-picture frame tree, then also activate the
+ // opener frame of the picture-in-picture root if it is same-origin with the
+ // activated frame.
+ FrameTree* pip_opener =
+ frame_tree().delegate()->GetDocumentPictureInPictureOpenerFrameTree();
+ if (pip_opener) {
+ RenderFrameHostImpl* opener_frame_host =
+ pip_opener->root()->current_frame_host();
+ if (opener_frame_host->GetLastCommittedOrigin().IsSameOriginWith(
+ current_origin)) {
+ opener_frame_host->DidReceiveUserActivation();
+ opener_frame_host->ActivateUserActivation(notification_type,
+ sticky_only);
+ }
+ }
+
// If we own a picture-in-picture window, then also activate same-origin
// frames within the picture-in-picture window.
FrameTree* picture_in_picture_frame_tree =
diff --git a/content/browser/renderer_host/ipc_utils.cc b/content/browser/renderer_host/ipc_utils.cc
index fd9deb4f..94ee783 100644
--- a/content/browser/renderer_host/ipc_utils.cc
+++ b/content/browser/renderer_host/ipc_utils.cc
@@ -394,6 +394,13 @@
return false;
}
+ if (params.pip_options &&
+ params.disposition != WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) {
+ bad_message::ReceivedBadMessage(
+ process, bad_message::RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS);
+ return false;
+ }
+
// Verify `form_submission_post_data`.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (!policy->CanReadRequestBody(process, params.form_submission_post_data)) {
diff --git a/content/browser/renderer_host/ipc_utils.h b/content/browser/renderer_host/ipc_utils.h
index 73d24681..b56bf12 100644
--- a/content/browser/renderer_host/ipc_utils.h
+++ b/content/browser/renderer_host/ipc_utils.h
@@ -6,6 +6,7 @@
#define CONTENT_BROWSER_RENDERER_HOST_IPC_UTILS_H_
#include "base/memory/scoped_refptr.h"
+#include "content/common/content_export.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/render_process_host.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -68,8 +69,9 @@
// CreateNewWindowParams are invalid.
//
// This function has to be called on the UI thread.
-bool VerifyCreateNewWindowParams(const RenderFrameHostImpl& current_rfh,
- const mojom::CreateNewWindowParams& params);
+CONTENT_EXPORT bool VerifyCreateNewWindowParams(
+ const RenderFrameHostImpl& current_rfh,
+ const mojom::CreateNewWindowParams& params);
// Verify that the initiator frame identified by `initiator_frame_token` and
// `initiator_process_id` can navigate `current_rfh`.
diff --git a/tools/metrics/histograms/metadata/stability/enums.xml b/tools/metrics/histograms/metadata/stability/enums.xml
index 803150e..d8bf5dc 100644
--- a/tools/metrics/histograms/metadata/stability/enums.xml
+++ b/tools/metrics/histograms/metadata/stability/enums.xml
@@ -531,6 +531,7 @@
label="BIBI_BIND_GAMEPAD_MONITOR_BLOCKED_BY_PERMISSIONS_POLICY"/>
<int value="357"
label="BIBI_BIND_GAMEPAD_HAPTICS_MANAGER_BLOCKED_BY_PERMISSIONS_POLICY"/>
+ <int value="358" label="RFH_CREATE_NEW_WINDOW_INVALID_PIP_OPTIONS"/>
</enum>
<enum name="BadMessageReasonExtensions">
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index 77b119d..146ac7b 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -13,12 +13,14 @@
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/ipc_utils.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
+#include "content/common/frame.mojom.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/dedicated_worker_service.h"
#include "content/public/browser/navigation_handle.h"
@@ -39,6 +41,7 @@
#include "content/shell/common/shell_switches.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
+#include "media/base/media_switches.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
@@ -1965,6 +1968,118 @@
EXPECT_FALSE(root->HasTransientUserActivation());
}
+class PictureInPictureFrameTreeBrowserTest
+ : public CrossProcessFrameTreeBrowserTest {
+ public:
+ PictureInPictureFrameTreeBrowserTest() {
+ scoped_feature_list_.InitWithFeatures(
+ {blink::features::kDocumentPictureInPictureUserActivation,
+ media::kDocumentPictureInPictureNavigation},
+ {});
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that user activation in a document picture-in-picture window only
+// propagates to the opener window when the activated frame is same-origin with
+// the opener's main frame.
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+ UserActivationOpenerPropagationSameOriginOnly) {
+ // Load an a.com page that embeds a cross-origin b.com subframe.
+ GURL main_url(embedded_test_server()->GetURL(
+ "a.com", "/cross_site_iframe_factory.html?a(b)"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ WebContentsImpl* opener_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ FrameTreeNode* opener_root = opener_contents->GetPrimaryFrameTree().root();
+ ASSERT_EQ(1U, opener_root->child_count());
+ FrameTreeNode* opener_child = opener_root->child_at(0);
+ ASSERT_FALSE(
+ opener_root->current_frame_host()
+ ->GetLastCommittedOrigin()
+ .IsSameOriginWith(
+ opener_child->current_frame_host()->GetLastCommittedOrigin()));
+
+ // Create a picture-in-picture window whose opener is the b.com subframe so
+ // that the picture-in-picture opener resolves to the embedding a.com page.
+ WebContents::CreateParams create_params(
+ opener_contents->GetBrowserContext(),
+ opener_child->current_frame_host()->GetSiteInstance());
+ create_params.picture_in_picture_options =
+ blink::mojom::PictureInPictureWindowOptions();
+ std::unique_ptr<WebContentsImpl> pip_contents =
+ WebContentsImpl::CreateWithOpener(create_params,
+ opener_child->current_frame_host());
+ ASSERT_EQ(&opener_contents->GetPrimaryFrameTree(),
+ pip_contents->GetDocumentPictureInPictureOpenerFrameTree());
+
+ // Navigate the picture-in-picture window to a b.com document so that its
+ // main frame is cross-origin with the opener's main frame.
+ GURL cross_origin_url(
+ embedded_test_server()->GetURL("b.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(pip_contents.get(), cross_origin_url));
+ FrameTreeNode* pip_root = pip_contents->GetPrimaryFrameTree().root();
+ ASSERT_FALSE(
+ pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+ opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+ EXPECT_FALSE(opener_root->HasStickyUserActivation());
+ EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+ // Activating the cross-origin picture-in-picture main frame must not
+ // propagate to the opener.
+ EXPECT_TRUE(pip_root->UpdateUserActivationState(
+ blink::mojom::UserActivationUpdateType::kNotifyActivation,
+ blink::mojom::UserActivationNotificationType::kTest));
+ EXPECT_TRUE(pip_root->HasStickyUserActivation());
+ EXPECT_TRUE(pip_root->HasTransientUserActivation());
+ EXPECT_FALSE(opener_root->HasStickyUserActivation());
+ EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+ // Navigate the picture-in-picture window to a same-origin a.com document and
+ // verify that activation does propagate to the opener.
+ GURL same_origin_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(pip_contents.get(), same_origin_url));
+ pip_root = pip_contents->GetPrimaryFrameTree().root();
+ ASSERT_TRUE(
+ pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+ opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+ EXPECT_TRUE(pip_root->UpdateUserActivationState(
+ blink::mojom::UserActivationUpdateType::kNotifyActivation,
+ blink::mojom::UserActivationNotificationType::kTest));
+ EXPECT_TRUE(opener_root->HasStickyUserActivation());
+ EXPECT_TRUE(opener_root->HasTransientUserActivation());
+}
+
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+ VerifyCreateNewWindowParamsInvalidPipOptions) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+
+ RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+ shell()->web_contents()->GetPrimaryMainFrame());
+
+ // Create invalid params: NEW_POPUP with pip_options.
+ mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
+ params->disposition = WindowOpenDisposition::NEW_POPUP;
+ params->pip_options = blink::mojom::PictureInPictureWindowOptions::New();
+
+ RenderProcessHostWatcher crash_observer(
+ rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+ // Call the validation function. It should return false and trigger bad
+ // message.
+ EXPECT_FALSE(VerifyCreateNewWindowParams(*rfh, *params));
+
+ // The process should be terminated.
+ crash_observer.Wait();
+ EXPECT_FALSE(crash_observer.did_exit_normally());
+}
+
class BrowserContextGroupSwapFrameTreeBrowserTest : public ContentBrowserTest {
public:
BrowserContextGroupSwapFrameTreeBrowserTest()
Regression Test / PoC
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index 77b119d..146ac7b 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -13,12 +13,14 @@
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/ipc_utils.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
+#include "content/common/frame.mojom.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/dedicated_worker_service.h"
#include "content/public/browser/navigation_handle.h"
@@ -39,6 +41,7 @@
#include "content/shell/common/shell_switches.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
+#include "media/base/media_switches.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
@@ -1965,6 +1968,118 @@
EXPECT_FALSE(root->HasTransientUserActivation());
}
+class PictureInPictureFrameTreeBrowserTest
+ : public CrossProcessFrameTreeBrowserTest {
+ public:
+ PictureInPictureFrameTreeBrowserTest() {
+ scoped_feature_list_.InitWithFeatures(
+ {blink::features::kDocumentPictureInPictureUserActivation,
+ media::kDocumentPictureInPictureNavigation},
+ {});
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that user activation in a document picture-in-picture window only
+// propagates to the opener window when the activated frame is same-origin with
+// the opener's main frame.
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+ UserActivationOpenerPropagationSameOriginOnly) {
+ // Load an a.com page that embeds a cross-origin b.com subframe.
+ GURL main_url(embedded_test_server()->GetURL(
+ "a.com", "/cross_site_iframe_factory.html?a(b)"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ WebContentsImpl* opener_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ FrameTreeNode* opener_root = opener_contents->GetPrimaryFrameTree().root();
+ ASSERT_EQ(1U, opener_root->child_count());
+ FrameTreeNode* opener_child = opener_root->child_at(0);
+ ASSERT_FALSE(
+ opener_root->current_frame_host()
+ ->GetLastCommittedOrigin()
+ .IsSameOriginWith(
+ opener_child->current_frame_host()->GetLastCommittedOrigin()));
+
+ // Create a picture-in-picture window whose opener is the b.com subframe so
+ // that the picture-in-picture opener resolves to the embedding a.com page.
+ WebContents::CreateParams create_params(
+ opener_contents->GetBrowserContext(),
+ opener_child->current_frame_host()->GetSiteInstance());
+ create_params.picture_in_picture_options =
+ blink::mojom::PictureInPictureWindowOptions();
+ std::unique_ptr<WebContentsImpl> pip_contents =
+ WebContentsImpl::CreateWithOpener(create_params,
+ opener_child->current_frame_host());
+ ASSERT_EQ(&opener_contents->GetPrimaryFrameTree(),
+ pip_contents->GetDocumentPictureInPictureOpenerFrameTree());
+
+ // Navigate the picture-in-picture window to a b.com document so that its
+ // main frame is cross-origin with the opener's main frame.
+ GURL cross_origin_url(
+ embedded_test_server()->GetURL("b.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(pip_contents.get(), cross_origin_url));
+ FrameTreeNode* pip_root = pip_contents->GetPrimaryFrameTree().root();
+ ASSERT_FALSE(
+ pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+ opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+ EXPECT_FALSE(opener_root->HasStickyUserActivation());
+ EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+ // Activating the cross-origin picture-in-picture main frame must not
+ // propagate to the opener.
+ EXPECT_TRUE(pip_root->UpdateUserActivationState(
+ blink::mojom::UserActivationUpdateType::kNotifyActivation,
+ blink::mojom::UserActivationNotificationType::kTest));
+ EXPECT_TRUE(pip_root->HasStickyUserActivation());
+ EXPECT_TRUE(pip_root->HasTransientUserActivation());
+ EXPECT_FALSE(opener_root->HasStickyUserActivation());
+ EXPECT_FALSE(opener_root->HasTransientUserActivation());
+
+ // Navigate the picture-in-picture window to a same-origin a.com document and
+ // verify that activation does propagate to the opener.
+ GURL same_origin_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+ EXPECT_TRUE(NavigateToURL(pip_contents.get(), same_origin_url));
+ pip_root = pip_contents->GetPrimaryFrameTree().root();
+ ASSERT_TRUE(
+ pip_root->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
+ opener_root->current_frame_host()->GetLastCommittedOrigin()));
+
+ EXPECT_TRUE(pip_root->UpdateUserActivationState(
+ blink::mojom::UserActivationUpdateType::kNotifyActivation,
+ blink::mojom::UserActivationNotificationType::kTest));
+ EXPECT_TRUE(opener_root->HasStickyUserActivation());
+ EXPECT_TRUE(opener_root->HasTransientUserActivation());
+}
+
+IN_PROC_BROWSER_TEST_F(PictureInPictureFrameTreeBrowserTest,
+ VerifyCreateNewWindowParamsInvalidPipOptions) {
+ EXPECT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+
+ RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+ shell()->web_contents()->GetPrimaryMainFrame());
+
+ // Create invalid params: NEW_POPUP with pip_options.
+ mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
+ params->disposition = WindowOpenDisposition::NEW_POPUP;
+ params->pip_options = blink::mojom::PictureInPictureWindowOptions::New();
+
+ RenderProcessHostWatcher crash_observer(
+ rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+ // Call the validation function. It should return false and trigger bad
+ // message.
+ EXPECT_FALSE(VerifyCreateNewWindowParams(*rfh, *params));
+
+ // The process should be terminated.
+ crash_observer.Wait();
+ EXPECT_FALSE(crash_observer.did_exit_normally());
+}
+
class BrowserContextGroupSwapFrameTreeBrowserTest : public ContentBrowserTest {
public:
BrowserContextGroupSwapFrameTreeBrowserTest()