blob: e6984cbc9ee43562aba1c015c2ba7a43fad4b021 [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 "chrome/browser/actor/aggregated_journal.h"
#include "base/memory/safe_ref.h"
#include "base/rand_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/actor/actor_logging.h"
#include "chrome/common/actor/journal_details_builder.h"
#include "chrome/common/chrome_render_frame.mojom.h"
#include "content/public/browser/render_frame_host_receiver_set.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
namespace actor {
namespace {
class JournalObserver : public mojom::JournalClient,
public content::WebContentsUserData<JournalObserver> {
public:
JournalObserver(const JournalObserver&) = delete;
JournalObserver& operator=(const JournalObserver&) = delete;
~JournalObserver() override {}
void EnsureJournalBound(content::RenderFrameHost& render_frame_host) {
if (journal_host_receivers_.IsBound(&render_frame_host)) {
return;
}
mojo::PendingAssociatedRemote<actor::mojom::JournalClient> client;
journal_host_receivers_.Bind(&render_frame_host,
client.InitWithNewEndpointAndPassReceiver());
mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> renderer;
render_frame_host.GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
renderer->StartActorJournal(std::move(client));
}
private:
friend class content::WebContentsUserData<JournalObserver>;
explicit JournalObserver(content::WebContents* web_contents,
base::SafeRef<AggregatedJournal> journal)
: content::WebContentsUserData<JournalObserver>(*web_contents),
journal_host_receivers_(web_contents, this),
journal_(journal) {}
// actor::mojom::JournalClient methods.
void AddEntriesToJournal(
std::vector<mojom::JournalEntryPtr> entries) override {
journal_->AppendJournalEntries(
journal_host_receivers_.GetCurrentTargetFrame(), std::move(entries));
}
content::RenderFrameHostReceiverSet<mojom::JournalClient>
journal_host_receivers_;
base::SafeRef<AggregatedJournal> journal_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
WEB_CONTENTS_USER_DATA_KEY_IMPL(JournalObserver);
} // namespace
AggregatedJournal::Entry::Entry(const std::string& location,
mojom::JournalEntryPtr data_arg)
: url(location), data(std::move(data_arg)) {}
AggregatedJournal::Entry::~Entry() = default;
AggregatedJournal::AggregatedJournal() = default;
AggregatedJournal::~AggregatedJournal() = default;
AggregatedJournal::PendingAsyncEntry::PendingAsyncEntry(
base::PassKey<AggregatedJournal> pass_key,
base::SafeRef<AggregatedJournal> journal,
TaskId task_id,
mojom::JournalTrack track,
std::string_view event_name)
: pass_key_(pass_key),
journal_(journal),
task_id_(task_id),
track_(track),
event_name_(event_name),
begin_time_(base::TimeTicks::Now()) {}
AggregatedJournal::PendingAsyncEntry::~PendingAsyncEntry() {
if (!terminated_) {
EndEntry({});
}
}
void AggregatedJournal::PendingAsyncEntry::EndEntry(
std::vector<mojom::JournalDetailsPtr> details) {
CHECK(!terminated_);
terminated_ = true;
ACTOR_LOG() << "End " << event_name_ << ": " << details;
journal_->AddEndEvent(pass_key_, task_id_, track_, event_name_,
std::move(details));
}
AggregatedJournal& AggregatedJournal::PendingAsyncEntry::GetJournal() {
return *journal_;
}
TaskId AggregatedJournal::PendingAsyncEntry::GetTaskId() {
return task_id_;
}
base::SafeRef<AggregatedJournal> AggregatedJournal::GetSafeRef() {
return weak_ptr_factory_.GetSafeRef();
}
std::unique_ptr<AggregatedJournal::PendingAsyncEntry>
AggregatedJournal::CreatePendingAsyncEntry(
const GURL& url,
TaskId task_id,
mojom::JournalTrack track,
std::string_view event_name,
std::vector<mojom::JournalDetailsPtr> details) {
ACTOR_LOG() << "Begin " << event_name << ": " << details;
AddEntry(std::make_unique<Entry>(
url.possibly_invalid_spec(),
mojom::JournalEntry::New(mojom::JournalEntryType::kBegin, task_id, track,
base::Time::Now(), std::string(event_name),
std::move(details))));
return base::WrapUnique(new PendingAsyncEntry(
base::PassKey<AggregatedJournal>(), weak_ptr_factory_.GetSafeRef(),
task_id, track, event_name));
}
void AggregatedJournal::Log(const GURL& url,
TaskId task_id,
mojom::JournalTrack track,
std::string_view event_name,
std::vector<mojom::JournalDetailsPtr> details) {
ACTOR_LOG() << event_name << ": " << details;
AddEntry(std::make_unique<Entry>(
url.possibly_invalid_spec(),
mojom::JournalEntry::New(mojom::JournalEntryType::kInstant, task_id,
track, base::Time::Now(),
std::string(event_name), std::move(details))));
}
void AggregatedJournal::EnsureJournalBound(content::RenderFrameHost& rfh) {
auto* web_contents = content::WebContents::FromRenderFrameHost(&rfh);
CHECK(web_contents);
auto* journal_observer = JournalObserver::FromWebContents(web_contents);
if (!journal_observer) {
JournalObserver::CreateForWebContents(web_contents,
weak_ptr_factory_.GetSafeRef());
journal_observer = JournalObserver::FromWebContents(web_contents);
}
journal_observer->EnsureJournalBound(rfh);
}
void AggregatedJournal::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void AggregatedJournal::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void AggregatedJournal::AppendJournalEntries(
content::RenderFrameHost* rfh,
std::vector<mojom::JournalEntryPtr> entries) {
std::string location = rfh->GetLastCommittedURL().possibly_invalid_spec();
for (auto& renderer_entry : entries) {
AddEntry(std::make_unique<Entry>(location, std::move(renderer_entry)));
}
}
void AggregatedJournal::AddEndEvent(
base::PassKey<AggregatedJournal> pass_key,
TaskId task_id,
mojom::JournalTrack track,
const std::string& event_name,
std::vector<mojom::JournalDetailsPtr> details) {
AddEntry(std::make_unique<Entry>(
std::string(), mojom::JournalEntry::New(mojom::JournalEntryType::kEnd,
task_id, track, base::Time::Now(),
event_name, std::move(details))));
}
void AggregatedJournal::LogScreenshot(const GURL& url,
TaskId task_id,
std::string_view mime_type,
base::span<const uint8_t> data) {
auto entry = std::make_unique<Entry>(
url.possibly_invalid_spec(),
mojom::JournalEntry::New(
mojom::JournalEntryType::kInstant, task_id,
mojom::JournalTrack::kActor, base::Time::Now(), "Screenshot",
/*details=*/std::vector<mojom::JournalDetailsPtr>()));
entry->screenshot.emplace(data.begin(), data.end());
AddEntry(std::move(entry));
}
void AggregatedJournal::LogAnnotatedPageContent(
const GURL& url,
TaskId task_id,
base::span<const uint8_t> data) {
auto entry = std::make_unique<Entry>(
url.possibly_invalid_spec(),
mojom::JournalEntry::New(
mojom::JournalEntryType::kInstant, task_id,
mojom::JournalTrack::kActor, base::Time::Now(), "PageContext",
/*details=*/std::vector<mojom::JournalDetailsPtr>()));
entry->annotated_page_content.emplace(data.begin(), data.end());
AddEntry(std::move(entry));
}
void AggregatedJournal::AddEntry(std::unique_ptr<Entry> new_entry) {
for (auto& observer : observers_) {
observer.WillAddJournalEntry(*new_entry);
}
entries_.SaveToBuffer(std::move(new_entry));
}
} // namespace actor