blob: 36ccd87c4c98e530c0f5f35fec1d47dfbd495fdb [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/surface_embed/browser/surface_embed_host.h"
#include <algorithm>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "components/guest_contents/browser/guest_contents_handle.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/surface_embed_connector.h"
#include "content/public/browser/web_contents.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
#include "third_party/blink/public/common/frame/frame_visual_properties.h"
namespace surface_embed {
// Owns all the SurfaceEmbedHost instances for a given document.
class SurfaceEmbedHostCollection
: public content::DocumentUserData<SurfaceEmbedHostCollection> {
public:
~SurfaceEmbedHostCollection() override;
SurfaceEmbedHostCollection(const SurfaceEmbedHostCollection&) = delete;
SurfaceEmbedHostCollection& operator=(const SurfaceEmbedHostCollection&) =
delete;
// Creates a new SurfaceEmbedHost and adds it to this collection.
SurfaceEmbedHost* CreateHost();
void RemoveHost(SurfaceEmbedHost* host);
private:
friend class content::DocumentUserData<SurfaceEmbedHostCollection>;
explicit SurfaceEmbedHostCollection(content::RenderFrameHost* rfh);
absl::flat_hash_set<std::unique_ptr<SurfaceEmbedHost>> hosts_;
DOCUMENT_USER_DATA_KEY_DECL();
};
DOCUMENT_USER_DATA_KEY_IMPL(SurfaceEmbedHostCollection);
SurfaceEmbedHostCollection::SurfaceEmbedHostCollection(
content::RenderFrameHost* rfh)
: DocumentUserData<SurfaceEmbedHostCollection>(rfh) {}
SurfaceEmbedHostCollection::~SurfaceEmbedHostCollection() = default;
SurfaceEmbedHost* SurfaceEmbedHostCollection::CreateHost() {
auto host = base::WrapUnique(new SurfaceEmbedHost(this));
SurfaceEmbedHost* host_ptr = host.get();
hosts_.insert(std::move(host));
return host_ptr;
}
void SurfaceEmbedHostCollection::RemoveHost(SurfaceEmbedHost* host) {
auto it = hosts_.find(host);
CHECK(it != hosts_.end());
hosts_.erase(it);
}
// static
SurfaceEmbedHost* SurfaceEmbedHost::Create(
content::RenderFrameHost* rfh,
mojo::PendingReceiver<mojom::SurfaceEmbedHost> receiver) {
auto* collection =
SurfaceEmbedHostCollection::GetOrCreateForCurrentDocument(rfh);
SurfaceEmbedHost* host = collection->CreateHost();
host->Bind(std::move(receiver));
return host;
}
SurfaceEmbedHost::SurfaceEmbedHost(SurfaceEmbedHostCollection* collection)
: collection_(*collection) {}
SurfaceEmbedHost::~SurfaceEmbedHost() {
DetachConnector();
if (destruction_callback_for_testing_) {
std::move(destruction_callback_for_testing_).Run();
}
}
void SurfaceEmbedHost::Bind(
mojo::PendingReceiver<mojom::SurfaceEmbedHost> receiver) {
CHECK(!receiver_.is_bound());
receiver_.Bind(std::move(receiver));
receiver_.set_disconnect_handler(base::BindOnce(
&SurfaceEmbedHost::OnMojoDisconnect, base::Unretained(this)));
}
void SurfaceEmbedHost::SetDestructionCallbackForTesting(
base::OnceClosure callback) {
destruction_callback_for_testing_ = std::move(callback);
}
void SurfaceEmbedHost::SetSurfaceEmbed(
mojo::PendingRemote<mojom::SurfaceEmbed> surface_embed) {
CHECK(!surface_embed_.is_bound());
surface_embed_.Bind(std::move(surface_embed));
surface_embed_.set_disconnect_handler(base::BindOnce(
&SurfaceEmbedHost::OnMojoDisconnect, base::Unretained(this)));
}
void SurfaceEmbedHost::AttachConnector(
const base::UnguessableToken& content_id) {
// Should never call attach without having a valid SurfaceEmbed remote already
// bound.
CHECK(surface_embed_);
CHECK(!content_id.is_empty());
guest_contents::GuestContentsHandle* guest_handle =
guest_contents::GuestContentsHandle::FromID(content_id);
CHECK(guest_handle);
content::WebContents* web_contents_to_attach = guest_handle->web_contents();
CHECK(web_contents_to_attach);
// If the child WebContents is already attached to a SurfaceEmbedConnector, we
// need to detach it first. Since we're detaching some other SurfaceEmbedHost,
// we need to notify it of the detachment so the host and
// SurfaceEmbedWebPlugin stay in sync.
if (auto* connector = web_contents_to_attach->GetSurfaceEmbedConnector()) {
connector->GetDelegate()->DetachedByHost();
CHECK(!web_contents_to_attach->GetSurfaceEmbedConnector());
}
// If this host already has a child attached, we need to detach it first. Note
// that this request comes from the parent side, so we don't notify the
// SurfaceEmbed as it initiated the detachment.
DetachConnector();
child_contents_ = web_contents_to_attach->GetWeakPtr();
content::WebContents* parent_web_contents =
content::WebContents::FromRenderFrameHost(
&collection_->render_frame_host());
content::SurfaceEmbedConnector::Attach(web_contents_to_attach,
parent_web_contents, this);
if (web_contents_to_attach->IsCrashed()) {
// The child process may have crashed before the renderer for the parent
// got the chance to attach it.
// TODO(surface-embed): Notify the plugin.
// surface_embed_->ChildProcessGone();
return;
}
auto* connector = GetConnector();
CHECK(connector->GetFrameSinkId().is_valid());
surface_embed_->SetFrameSinkId(connector->GetFrameSinkId());
// TODO(surface-embed): If accessibility info was received before the
// connector was attached, pass it to the connector now.
}
void SurfaceEmbedHost::DetachConnector() {
if (GetConnector()) {
content::SurfaceEmbedConnector::Detach(child_contents_.get());
child_contents_ = nullptr;
}
}
void SurfaceEmbedHost::SynchronizeVisualProperties(
const blink::FrameVisualProperties& visual_properties,
bool is_visible) {
CHECK(surface_embed_.is_bound());
if (!is_visible) {
return;
}
if (auto* connector = GetConnector()) {
connector->OnSynchronizeVisualProperties(visual_properties);
}
}
void SurfaceEmbedHost::SetFrameSinkId(const viz::FrameSinkId& frame_sink_id) {
if (surface_embed_) {
surface_embed_->SetFrameSinkId(frame_sink_id);
}
}
void SurfaceEmbedHost::UpdateLocalSurfaceIdFromChild(
const viz::LocalSurfaceId& local_surface_id) {
if (surface_embed_) {
surface_embed_->UpdateLocalSurfaceIdFromChild(local_surface_id);
}
}
void SurfaceEmbedHost::DetachedByHost() {
// We're being forcibly detached (child being re-attached elsewhere).
CHECK(child_contents_);
// TODO(surface-embed): Notify the renderer's SurfaceEmbedWebPlugin that the
// host initiated the detachment.
DetachConnector();
}
bool SurfaceEmbedHost::IsAttachedForTesting() const {
return child_contents_ != nullptr;
}
void SurfaceEmbedHost::OnMojoDisconnect() {
// Reset both pipes. This will prevent the other disconnect handler from being
// called if it was also scheduled to run. The first one wins.
receiver_.reset();
surface_embed_.reset();
collection_->RemoveHost(this);
}
content::SurfaceEmbedConnector* SurfaceEmbedHost::GetConnector() const {
return child_contents_ ? child_contents_->GetSurfaceEmbedConnector()
: nullptr;
}
} // namespace surface_embed