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 <[email protected]> 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 <[email protected]> Commit-Queue: Tzarial <[email protected]> Reviewed-by: Rakina Zata Amni <[email protected]> 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()