blob: 615d0d7277cba238db2ab1a9ed15eed27e3a0730 [file] [log] [blame]
// Copyright 2025 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/optimization_guide/content/browser/page_content_metadata_observer.h"
#include "components/optimization_guide/content/browser/page_content_proto_util.h"
#include "content/public/browser/page.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/content_extraction/ai_page_content_metadata.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/opengraph/metadata.mojom.h"
namespace optimization_guide {
PageContentMetadataObserver::PageContentMetadataObserver(
content::WebContents* web_contents,
const std::vector<std::string>& names,
OnPageMetadataChangedCallback callback)
: content::WebContentsObserver(web_contents),
names_(names),
callback_(std::move(callback)) {
DCHECK(!names_.empty());
UpdateFrameObservers();
}
PageContentMetadataObserver::~PageContentMetadataObserver() = default;
void PageContentMetadataObserver::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
if (&render_frame_host->GetPage() != &web_contents()->GetPrimaryPage()) {
return;
}
if (frame_data_.contains(render_frame_host)) {
return;
}
if (!render_frame_host->IsRenderFrameLive()) {
return;
}
mojo::Remote<blink::mojom::FrameMetadataObserverRegistry> registry;
render_frame_host->GetRemoteInterfaces()->GetInterface(
registry.BindNewPipeAndPassReceiver());
mojo::PendingRemote<blink::mojom::MetaTagsObserver> observer_remote;
auto observer_receiver = observer_remote.InitWithNewPipeAndPassReceiver();
// The renderer will use this remote to call the browser.
registry->AddMetaTagsObserver(names_, std::move(observer_remote));
frame_data_.try_emplace(
render_frame_host,
std::make_unique<FrameMetaTagsObserver>(this, render_frame_host,
std::move(observer_receiver)));
}
void PageContentMetadataObserver::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
// A frame was removed, so we should notify the observer that the page
// metadata has changed if it had metadata.
auto it = frame_data_.find(render_frame_host);
if (it == frame_data_.end()) {
return;
}
bool had_metadata = !!it->second.metadata;
frame_data_.erase(it);
if (web_contents()->IsBeingDestroyed()) {
// No need to notify of changes if the web contents is going away.
return;
}
if (had_metadata) {
OnMetaTagsChangedForFrame(nullptr, {});
}
}
void PageContentMetadataObserver::PrimaryPageChanged(content::Page& page) {
// The primary page has changed, so we need to reset all frame observers
// and re-initialize them for the new page's frame tree.
frame_data_.clear();
UpdateFrameObservers();
}
void PageContentMetadataObserver::UpdateFrameObservers() {
auto* primary_main_frame = web_contents()->GetPrimaryMainFrame();
if (!primary_main_frame) {
return;
}
primary_main_frame->ForEachRenderFrameHost(
[this](content::RenderFrameHost* rfh) {
if (rfh->IsRenderFrameLive()) {
// RenderFrameCreated has the logic to create the observer and add it
// to the map if it doesn't exist.
RenderFrameCreated(rfh);
}
});
}
void PageContentMetadataObserver::DispatchMetadata() {
auto page_metadata = blink::mojom::PageMetadata::New();
for (const auto& it : frame_data_) {
if (it.second.metadata) {
page_metadata->frame_metadata.push_back(it.second.metadata->Clone());
}
}
callback_.Run(std::move(page_metadata));
}
void PageContentMetadataObserver::OnMetaTagsChangedForFrame(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::MetaTagPtr> meta_tags) {
// `render_frame_host` can be null when a frame is deleted, when this happens
// do not update the frame data, but do notify the callback.
if (render_frame_host) {
DCHECK(render_frame_host->GetPage().IsPrimary());
auto it = frame_data_.find(render_frame_host);
if (it != frame_data_.end()) {
if (meta_tags.empty()) {
it->second.metadata.reset();
} else {
auto frame_metadata = blink::mojom::FrameMetadata::New();
frame_metadata->url =
GetURLForFrameMetadata(render_frame_host->GetLastCommittedURL(),
render_frame_host->GetLastCommittedOrigin());
frame_metadata->meta_tags = std::move(meta_tags);
it->second.metadata = std::move(frame_metadata);
}
}
}
// The callback should always be valid, as it is a required parameter for
// creation.
DCHECK(callback_);
DispatchMetadata();
}
PageContentMetadataObserver::FrameData::FrameData(
std::unique_ptr<FrameMetaTagsObserver> observer)
: observer(std::move(observer)) {}
PageContentMetadataObserver::FrameData::~FrameData() = default;
PageContentMetadataObserver::FrameData::FrameData(FrameData&&) = default;
PageContentMetadataObserver::FrameData&
PageContentMetadataObserver::FrameData::operator=(FrameData&&) = default;
PageContentMetadataObserver::FrameMetaTagsObserver::FrameMetaTagsObserver(
PageContentMetadataObserver* owner,
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::MetaTagsObserver> receiver)
: owner_(owner),
render_frame_host_(render_frame_host),
receiver_(this, std::move(receiver)) {}
PageContentMetadataObserver::FrameMetaTagsObserver::~FrameMetaTagsObserver() =
default;
void PageContentMetadataObserver::FrameMetaTagsObserver::OnMetaTagsChanged(
std::vector<::blink::mojom::MetaTagPtr> meta_tags) {
owner_->OnMetaTagsChangedForFrame(render_frame_host_, std::move(meta_tags));
}
} // namespace optimization_guide