blob: 9b20685d417423cd57c688282ccec2e57b568e73 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_contents/partitioned_popins_controller.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/page.h"
namespace content {
namespace {
// Closes the WebContents.
void CloseWebContents(base::WeakPtr<WebContents> web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (web_contents) {
web_contents->ClosePage();
}
}
// Closes the popin on a new task.
void SchedulePopinClose(WebContents* popin_web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!popin_web_contents) {
return;
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&CloseWebContents, popin_web_contents->GetWeakPtr()));
}
// Checks if the popin opener is a descendant of the provided render frame
// host, including when they are the same.
bool IsOpenerDescendantOfRenderFrameHost(RenderFrameHost* opener,
RenderFrameHost* render_frame_host) {
if (!opener || !render_frame_host) {
return false;
}
for (RenderFrameHost* current = opener; current;
current = current->GetParentOrOuterDocumentOrEmbedder()) {
if (current == render_frame_host) {
return true;
}
}
return false;
}
WebContentsImpl* GetPopin(WebContents* web_contents) {
if (!web_contents) {
return nullptr;
}
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents);
return static_cast<WebContentsImpl*>(
web_contents_impl->GetOpenedPartitionedPopin());
}
} // namespace
WEB_CONTENTS_USER_DATA_KEY_IMPL(PartitionedPopinsController);
PartitionedPopinsController::PartitionedPopinsController(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<PartitionedPopinsController>(*web_contents) {
}
// Closes the popin if the opener's Render Frame is deleted.
void PartitionedPopinsController::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContentsImpl* popin = GetPopin(web_contents());
if (popin &&
render_frame_host == popin->GetPartitionedPopinOpener(PassKey())) {
SchedulePopinClose(popin);
}
}
// Closes the popin if the opener render frame host is changed.
void PartitionedPopinsController::RenderFrameHostChanged(
RenderFrameHost* old_host,
RenderFrameHost* new_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContentsImpl* popin = GetPopin(web_contents());
if (popin && old_host == popin->GetPartitionedPopinOpener(PassKey())) {
SchedulePopinClose(popin);
}
}
// Closes the popin if the opener frame is deleted.
void PartitionedPopinsController::FrameDeleted(
FrameTreeNodeId frame_tree_node_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContentsImpl* popin = GetPopin(web_contents());
if (popin && popin->GetPartitionedPopinOpener(PassKey()) ==
web_contents()->UnsafeFindFrameByFrameTreeNodeId(
frame_tree_node_id)) {
SchedulePopinClose(popin);
}
}
// Closes the popin if the opener frame navigates.
// TODO(crbug.com/375653317): Revisit if same-document navigation should be
// allowed. If same-document navigations are allowed then the ancestor checking
// done by `IsOpenerDescendantOfRenderFrameHost` below can be removed in favor
// of `FrameDeleted`.
void PartitionedPopinsController::DidFinishNavigation(
NavigationHandle* navigation_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!navigation_handle->HasCommitted()) {
return;
}
WebContentsImpl* popin = GetPopin(web_contents());
if (popin &&
// Ancestor navigation is checked to close popins if the opener's ancestor
// do a same-document navigation – e.g. a single-page application updates
// the history – that is not handled by the `FrameDeleted` listener.
IsOpenerDescendantOfRenderFrameHost(
popin->GetPartitionedPopinOpener(PassKey()),
navigation_handle->GetRenderFrameHost())) {
SchedulePopinClose(popin);
}
}
// Closes the popin if the WebContents is destroyed.
void PartitionedPopinsController::WebContentsDestroyed() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContentsImpl* popin = GetPopin(web_contents());
if (popin) {
SchedulePopinClose(popin);
}
}
// Closes the popin if the Page of the opener's WebContents changes.
void PartitionedPopinsController::PrimaryPageChanged(Page& page) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContentsImpl* popin = GetPopin(web_contents());
if (popin) {
SchedulePopinClose(popin);
}
}
PartitionedPopinsController::~PartitionedPopinsController() = default;
} // namespace content