blob: 82f7f51db6f8d7645bb4edbc41d19f2883f62fc3 [file] [log] [blame] [edit]
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_EXTRACTION_FRAME_METADATA_OBSERVER_REGISTRY_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_EXTRACTION_FRAME_METADATA_OBSERVER_REGISTRY_H_
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "third_party/blink/public/mojom/content_extraction/ai_page_content_metadata.mojom-blink.h"
#include "third_party/blink/public/mojom/content_extraction/frame_metadata_observer_registry.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_mutation_observer_init.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/mutation_observer.h"
#include "third_party/blink/renderer/core/dom/mutation_record.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/html/html_head_element.h"
#include "third_party/blink/renderer/core/html/html_meta_element.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote_set.h"
#include "third_party/blink/renderer/platform/supplementable.h"
namespace blink {
class LocalFrame;
// Registry used to Add Observers for when frame metadata changes.
class MODULES_EXPORT FrameMetadataObserverRegistry final
: public GarbageCollected<FrameMetadataObserverRegistry>,
public mojom::blink::FrameMetadataObserverRegistry,
public GarbageCollectedMixin {
public:
static FrameMetadataObserverRegistry* From(Document&);
static void BindReceiver(
LocalFrame* frame,
mojo::PendingReceiver<mojom::blink::FrameMetadataObserverRegistry>
receiver);
FrameMetadataObserverRegistry(base::PassKey<FrameMetadataObserverRegistry>,
LocalFrame&);
FrameMetadataObserverRegistry(const FrameMetadataObserverRegistry&) = delete;
FrameMetadataObserverRegistry& operator=(
const FrameMetadataObserverRegistry&) = delete;
~FrameMetadataObserverRegistry() override;
void Trace(Visitor* visitor) const override;
// mojom::blink::FrameMetadataObserverRegistry:
void AddPaidContentMetadataObserver(
mojo::PendingRemote<mojom::blink::PaidContentMetadataObserver> observer)
override;
void AddMetaTagsObserver(
const Vector<String>& names,
mojo::PendingRemote<mojom::blink::MetaTagsObserver> observer) override;
private:
struct MetaTagsObserverTraits;
struct PaidContentObserverTraits;
template <typename Traits>
class FrameMetadataMutationObserver;
class DomContentLoadedListener;
class MetaTagAttributeObserver;
class PaidContentAttributeObserver;
friend class DomContentLoadedListener;
struct MetaTagsObserverTraits {
using ElementType = HTMLMetaElement;
static void OnChanged(FrameMetadataObserverRegistry* registry) {
registry->OnMetaTagsChanged();
}
static void ObserveAttributes(FrameMetadataObserverRegistry* registry,
ElementType* element) {
registry->ObserveMetaTagAttributes(element);
}
static void StopObservingAttributes(FrameMetadataObserverRegistry* registry,
ElementType* element) {
registry->StopObservingMetaTagAttributes(element);
}
static void DisconnectAllAttributeObservers(
FrameMetadataObserverRegistry* registry) {
registry->DisconnectAllAttributeObservers();
}
};
struct PaidContentObserverTraits {
using ElementType = HTMLScriptElement;
static void OnChanged(FrameMetadataObserverRegistry* registry) {
registry->OnPaidContentMetadataChanged();
}
static void ObserveAttributes(FrameMetadataObserverRegistry* registry,
ElementType* element) {
registry->ObservePaidContentScriptAttributes(element);
}
static void StopObservingAttributes(FrameMetadataObserverRegistry* registry,
ElementType* element) {
registry->StopObservingPaidContentScriptAttributes(element);
}
static void DisconnectAllAttributeObservers(
FrameMetadataObserverRegistry* registry) {
registry->DisconnectAllPaidContentAttributeObservers();
}
};
template <typename Traits>
class FrameMetadataMutationObserver final
: public MutationObserver::Delegate {
public:
explicit FrameMetadataMutationObserver(
FrameMetadataObserverRegistry* registry)
: registry_(registry), observer_(MutationObserver::Create(this)) {}
void ObserveHead(HTMLHeadElement* head) {
if (observing_.Get() == head) {
return;
}
Disconnect();
observing_ = head;
if (!head) {
return;
}
// Start observing childList changes in the head.
MutationObserverInit* init = MutationObserverInit::Create();
init->setChildList(true);
init->setSubtree(true);
DummyExceptionStateForTesting exception_state;
observer_->observe(head, init, exception_state);
DCHECK(!exception_state.HadException());
// For all existing elements, set up attribute observers.
for (typename Traits::ElementType& element :
Traversal<typename Traits::ElementType>::ChildrenOf(*head)) {
Traits::ObserveAttributes(registry_, &element);
}
}
void ObserveDocument(Element* document_element) {
if (observing_.Get() == document_element) {
return;
}
observer_->disconnect();
MutationObserverInit* init = MutationObserverInit::Create();
init->setChildList(true);
DummyExceptionStateForTesting exception_state;
observer_->observe(document_element, init, exception_state);
DCHECK(!exception_state.HadException());
observing_ = document_element;
}
void Disconnect() {
observer_->disconnect();
observing_ = nullptr;
Traits::DisconnectAllAttributeObservers(registry_);
}
ExecutionContext* GetExecutionContext() const override {
return registry_->document_->GetExecutionContext();
}
void Deliver(const HeapVector<Member<MutationRecord>>& records,
MutationObserver&) override {
bool needs_update = false;
for (const auto& record : records) {
if (record->type() == "childList") {
// This handles the case where the <head> element itself is added to
// the doc.
for (unsigned i = 0; i < record->addedNodes()->length(); ++i) {
if (IsA<HTMLHeadElement>(record->addedNodes()->item(i))) {
Traits::OnChanged(registry_);
return;
}
}
// This handles meta tags added/removed inside the head.
for (unsigned i = 0; i < record->addedNodes()->length(); ++i) {
if (auto* element = DynamicTo<typename Traits::ElementType>(
record->addedNodes()->item(i))) {
Traits::ObserveAttributes(registry_, element);
needs_update = true;
}
}
for (unsigned i = 0; i < record->removedNodes()->length(); ++i) {
if (auto* element = DynamicTo<typename Traits::ElementType>(
record->removedNodes()->item(i))) {
Traits::StopObservingAttributes(registry_, element);
needs_update = true;
}
}
}
}
if (needs_update) {
Traits::OnChanged(registry_);
}
}
void Trace(Visitor* visitor) const override {
visitor->Trace(registry_);
visitor->Trace(observer_);
visitor->Trace(observing_);
MutationObserver::Delegate::Trace(visitor);
}
private:
Member<FrameMetadataObserverRegistry> registry_;
Member<MutationObserver> observer_;
WeakMember<Node> observing_;
};
void Bind(mojo::PendingReceiver<mojom::blink::FrameMetadataObserverRegistry>
receiver);
void DisconnectAllAttributeObservers();
void ObserveMetaTagAttributes(HTMLMetaElement* meta);
void StopObservingMetaTagAttributes(HTMLMetaElement* meta);
void DisconnectAllPaidContentAttributeObservers();
void ObservePaidContentScriptAttributes(HTMLScriptElement* script);
void StopObservingPaidContentScriptAttributes(HTMLScriptElement* script);
void OnDomContentLoaded();
void OnPaidContentMetadataChanged();
void OnMetaTagsChanged();
// Returns true if there are observers.
bool UpdateMetaTagsObserver();
// Returns true if there are observers.
bool UpdatePaidContentObserver();
void ListenForDomContentLoaded();
void DisconnectHandler(mojo::RemoteSetElementId);
void PaidContentDisconnectHandler(mojo::RemoteSetElementId);
Member<Document> document_;
HeapMojoReceiverSet<mojom::blink::FrameMetadataObserverRegistry,
FrameMetadataObserverRegistry>
receiver_set_;
HeapMojoRemoteSet<mojom::blink::PaidContentMetadataObserver>
paid_content_metadata_observers_;
HeapMojoRemoteSet<mojom::blink::MetaTagsObserver> metatags_observers_;
struct MetaTagsObserverData : public GarbageCollected<MetaTagsObserverData> {
void Trace(Visitor* visitor) const { visitor->Trace(names_to_observe); }
HeapVector<String> names_to_observe;
Vector<mojom::blink::MetaTagPtr> last_sent_meta_tags;
bool sent_initial_update = false;
};
// Data for each metatags observer, keyed by RemoteSetElementId.
HeapHashMap<uint32_t, Member<MetaTagsObserverData>>
remote_id_to_observer_data_;
// A map from metatag name to the number of observers that are interested in
// it.
HashMap<String, int> all_metatag_name_counts_;
Member<DomContentLoadedListener> dom_content_loaded_observer_;
Member<FrameMetadataMutationObserver<MetaTagsObserverTraits>>
meta_tags_mutation_observer_;
Member<FrameMetadataMutationObserver<PaidContentObserverTraits>>
paid_content_mutation_observer_;
HeapHashMap<WeakMember<HTMLMetaElement>, Member<MutationObserver>>
meta_tag_attribute_observers_;
HeapHashMap<WeakMember<HTMLScriptElement>, Member<MutationObserver>>
paid_content_attribute_observers_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_EXTRACTION_FRAME_METADATA_OBSERVER_REGISTRY_H_