blob: 07e89cfda6d8a946b98828915849dc6aee4392e0 [file]
// Copyright 2017 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/performance_manager/graph/page_node_impl.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/byte_size.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/typed_macros.h"
#include "base/unguessable_token.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/graph_impl.h"
#include "components/performance_manager/graph/graph_impl_operations.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/public/graph/graph_operations.h"
#include "components/performance_manager/public/render_frame_host_proxy.h"
#include "components/performance_manager/public/resource_attribution/page_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/perfetto/include/perfetto/tracing/tracing.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
namespace performance_manager {
namespace {
using perfetto::protos::pbzero::ChromeTrackEvent;
perfetto::StaticString PageNodeLoadingStateToString(
const PageNode::LoadingState& loading_state) {
switch (loading_state) {
case PageNode::LoadingState::kLoadingNotStarted:
return nullptr;
case PageNode::LoadingState::kLoading:
return "Loading";
case PageNode::LoadingState::kLoadingTimedOut:
return "LoadingTimedOut";
case PageNode::LoadingState::kLoadedBusy:
return "LoadedBusy";
case PageNode::LoadingState::kLoadedIdle:
return nullptr;
}
NOTREACHED();
}
perfetto::StaticString PageNodeVisibilityToString(const bool& is_visible) {
if (is_visible) {
return "Visible";
} else {
return nullptr;
}
}
} // namespace
PageNodeImpl::PageNodeImpl(base::WeakPtr<content::WebContents> web_contents,
const content::WebContents::UniqueToken& page_token,
const base::UnguessableToken& browser_context_id,
const GURL& visible_url,
PagePropertyFlags initial_properties,
base::TimeTicks visibility_change_time,
const perfetto::Track& tracing_track)
: web_contents_(std::move(web_contents)),
page_token_(page_token),
frames_track_("Frames", 0, tracing_track),
visibility_change_time_(visibility_change_time),
main_frame_url_(visible_url),
browser_context_id_(browser_context_id),
is_focused_(false,
perfetto::StateTrack("IsFocused", 0, tracing_track),
YesNoStateToString),
is_visible_(initial_properties.Has(PagePropertyFlag::kIsVisible),
perfetto::StateTrack("Visibility", 0, tracing_track),
PageNodeVisibilityToString),
is_audible_(initial_properties.Has(PagePropertyFlag::kIsAudible),
perfetto::StateTrack("IsAudible", 0, tracing_track),
YesNoStateToString),
has_picture_in_picture_(
initial_properties.Has(PagePropertyFlag::kHasPictureInPicture)),
is_off_the_record_(
initial_properties.Has(PagePropertyFlag::kIsOffTheRecord)),
loading_state_(LoadingState::kLoadingNotStarted,
perfetto::StateTrack("LoadingState", 0, tracing_track),
PageNodeLoadingStateToString) {
// The `PageNodeImpl` creation hook is before the `WebContents`' visible or
// committed url can be set, so the initial main frame URL is always empty.
// TODO(crbug.com/40121561): Remove `visible_url` from the constructor in M132
// if no issues are found with this CHECK.
CHECK(main_frame_url_.value().is_empty());
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_audible_.value()) {
audible_change_time_ = base::TimeTicks::Now();
}
}
PageNodeImpl::~PageNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(nullptr, opener_frame_node_);
DCHECK_EQ(nullptr, embedder_frame_node_);
}
const base::UnguessableToken& PageNodeImpl::GetBrowserContextID() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return browser_context_id_;
}
resource_attribution::PageContext PageNodeImpl::GetResourceContext() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return resource_attribution::PageContext::FromPageNode(this);
}
PageType PageNodeImpl::GetType() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return type_.value();
}
bool PageNodeImpl::IsFocused() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_focused_.value();
}
bool PageNodeImpl::IsVisible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_visible_.value();
}
base::TimeTicks PageNodeImpl::GetLastVisibilityChangeTime() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return visibility_change_time_;
}
bool PageNodeImpl::IsAudible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_audible_.value();
}
std::optional<base::TimeDelta> PageNodeImpl::GetTimeSinceLastAudibleChange()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (audible_change_time_.has_value()) {
return base::TimeTicks::Now() - audible_change_time_.value();
}
return std::nullopt;
}
bool PageNodeImpl::HasPictureInPicture() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return has_picture_in_picture_.value();
}
bool PageNodeImpl::HasFreezingOriginTrialOptOut() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return has_freezing_origin_trial_opt_out_.value();
}
bool PageNodeImpl::IsOffTheRecord() const {
return is_off_the_record_;
}
PageNode::LoadingState PageNodeImpl::GetLoadingState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return loading_state_.value();
}
ukm::SourceId PageNodeImpl::GetUkmSourceID() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ukm_source_id_.value();
}
PageNodeImpl::LifecycleState PageNodeImpl::GetLifecycleState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state_.value();
}
bool PageNodeImpl::IsHoldingWebLock() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_holding_weblock_.value();
}
bool PageNodeImpl::IsHoldingBlockingIndexedDBLock() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_holding_blocking_indexeddb_lock_.value();
}
bool PageNodeImpl::UsesWebRTC() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return uses_web_rtc_.value();
}
int64_t PageNodeImpl::GetNavigationID() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return navigation_id_;
}
const std::string& PageNodeImpl::GetContentsMimeType() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return contents_mime_type_;
}
std::optional<blink::mojom::PermissionStatus>
PageNodeImpl::GetNotificationPermissionStatus() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return notification_permission_status_.value();
}
base::TimeDelta PageNodeImpl::GetTimeSinceLastNavigation() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (navigation_committed_time_.is_null()) {
return base::TimeDelta();
}
return base::TimeTicks::Now() - navigation_committed_time_;
}
const GURL& PageNodeImpl::GetMainFrameUrl() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return main_frame_url_.value();
}
base::ByteSize PageNodeImpl::EstimateMainFramePrivateFootprintSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ByteSize total;
FrameNodeImpl* main_frame = primary_main_frame_node();
if (main_frame) {
performance_manager::GraphImplOperations::VisitFrameAndChildrenPreOrder(
main_frame, [&total](FrameNodeImpl* frame_node) {
total += frame_node->GetPrivateFootprintEstimate();
return true;
});
}
return total;
}
bool PageNodeImpl::HadFormInteraction() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return had_form_interaction_.value();
}
bool PageNodeImpl::HadUserEdits() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return had_user_edits_.value();
}
base::WeakPtr<content::WebContents> PageNodeImpl::GetWebContents() const {
return web_contents_;
}
base::ByteSize PageNodeImpl::EstimateResidentSetSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ByteSize total;
performance_manager::GraphOperations::VisitFrameTreePreOrder(
this, [&total](const FrameNode* frame_node) {
total += frame_node->GetResidentSetEstimate();
return true;
});
return total;
}
base::ByteSize PageNodeImpl::EstimatePrivateFootprintSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ByteSize total;
performance_manager::GraphOperations::VisitFrameTreePreOrder(
this, [&total](const FrameNode* frame_node) {
total += frame_node->GetPrivateFootprintEstimate();
return true;
});
return total;
}
base::WeakPtr<PageNode> PageNodeImpl::GetWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_factory_.GetWeakPtr();
}
base::WeakPtr<const PageNode> PageNodeImpl::GetWeakPtr() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_factory_.GetWeakPtr();
}
void PageNodeImpl::AddFrame(base::PassKey<FrameNodeImpl>,
FrameNodeImpl* frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_node);
DCHECK_EQ(this, frame_node->page_node());
DCHECK(graph()->NodeInGraph(frame_node));
++frame_node_count_;
if (frame_node->parent_frame_node() == nullptr) {
main_frame_nodes_.insert(frame_node);
}
}
void PageNodeImpl::TraceFrame(base::PassKey<FrameNodeImpl>,
FrameNodeImpl* frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_node);
DCHECK(graph()->NodeInGraph(frame_node));
const char* event_name =
frame_node->IsMainFrame() ? "MainFrameAttached" : "FrameAttached";
TRACE_EVENT_INSTANT("performance_manager.graph",
perfetto::StaticString(event_name), frames_track_,
perfetto::Flow::Global(base::UnguessableTokenHash()(
frame_node->GetFrameToken().value())));
}
void PageNodeImpl::RemoveFrame(base::PassKey<FrameNodeImpl>,
FrameNodeImpl* frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_node);
DCHECK_EQ(this, frame_node->page_node());
DCHECK(graph()->NodeInGraph(frame_node));
--frame_node_count_;
if (frame_node->parent_frame_node() == nullptr) {
size_t removed = main_frame_nodes_.erase(frame_node);
DCHECK_EQ(1u, removed);
}
const char* event_name =
frame_node->IsMainFrame() ? "MainFrameDetached" : "FrameDetached";
TRACE_EVENT_INSTANT(
"performance_manager.graph", perfetto::StaticString(event_name),
frames_track_,
perfetto::TerminatingFlow::Global(
base::UnguessableTokenHash()(frame_node->GetFrameToken().value())));
}
void PageNodeImpl::SetLoadingState(LoadingState loading_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
loading_state_.SetAndMaybeNotify(this, loading_state);
}
void PageNodeImpl::SetType(PageType type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_NE(type, PageType::kUnknown);
CHECK_EQ(type_.value(), PageType::kUnknown);
type_.SetAndMaybeNotify(this, type);
}
void PageNodeImpl::SetIsFocused(bool is_focused) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_focused_.SetAndMaybeNotify(this, is_focused);
}
void PageNodeImpl::SetIsVisible(bool is_visible) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_visible_.SetAndMaybeNotify(this, is_visible)) {
// The change time needs to be updated after observers are notified, as they
// use this to determine time passed since the *previous* visibility state
// change. They can infer the current state change time themselves via
// NowTicks.
visibility_change_time_ = base::TimeTicks::Now();
}
}
void PageNodeImpl::SetIsAudible(bool is_audible) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_audible_.SetAndMaybeNotify(this, is_audible)) {
// The change time needs to be updated after observers are notified, as they
// use this to determine time passed since the *previous* state change. They
// can infer the current state change time themselves via NowTicks.
audible_change_time_ = base::TimeTicks::Now();
}
}
void PageNodeImpl::SetHasPictureInPicture(bool has_picture_in_picture) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_picture_in_picture_.SetAndMaybeNotify(this, has_picture_in_picture);
}
void PageNodeImpl::SetUkmSourceId(ukm::SourceId ukm_source_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ukm_source_id_.SetAndMaybeNotify(this, ukm_source_id);
}
void PageNodeImpl::OnFaviconUpdated(blink::mojom::FaviconUpdateReason reason) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : GetObservers()) {
observer.OnFaviconUpdated(this, reason);
}
}
void PageNodeImpl::OnTitleUpdated() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : GetObservers()) {
observer.OnTitleUpdated(this);
}
}
void PageNodeImpl::OnAboutToBeDiscarded(base::WeakPtr<PageNode> new_page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!embedder_frame_node_)
<< "Discard should only be called on the primary page node.";
if (!new_page_node) {
return;
}
// Notify all embedded frames that the primary page is about to be discarded.
if (FrameNodeImpl* main_frame = primary_main_frame_node()) {
main_frame->OnPrimaryPageAboutToBeDiscarded();
}
for (auto& observer : GetObservers()) {
observer.OnAboutToBeDiscarded(this, new_page_node.get());
}
}
void PageNodeImpl::SetMainFrameRestoredState(
const GURL& url,
blink::mojom::PermissionStatus notification_permission_status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(main_frame_url_.value().is_empty());
EmitMainFrameUrlChangedEvent(url);
main_frame_url_.SetAndMaybeNotify(this, url);
notification_permission_status_.SetAndMaybeNotify(
this, notification_permission_status);
}
void PageNodeImpl::OnMainFrameNavigationCommitted(
bool same_document,
base::TimeTicks navigation_committed_time,
int64_t navigation_id,
const GURL& url,
const std::string& contents_mime_type,
std::optional<blink::mojom::PermissionStatus>
notification_permission_status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This should never be invoked with a null navigation, nor should it be
// called twice for the same navigation.
DCHECK_NE(0, navigation_id);
DCHECK_NE(navigation_id_, navigation_id);
navigation_committed_time_ = navigation_committed_time;
navigation_id_ = navigation_id;
contents_mime_type_ = contents_mime_type;
if (url != main_frame_url_.value()) {
EmitMainFrameUrlChangedEvent(url, navigation_id);
}
main_frame_url_.SetAndMaybeNotify(this, url);
notification_permission_status_.SetAndMaybeNotify(
this, notification_permission_status);
// No mainframe document change notification on same-document navigations.
if (same_document) {
return;
}
for (auto& observer : GetObservers()) {
observer.OnMainFrameDocumentChanged(this);
}
}
void PageNodeImpl::OnNotificationPermissionStatusChange(
blink::mojom::PermissionStatus permission_status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
notification_permission_status_.SetAndMaybeNotify(this, permission_status);
}
FrameNodeImpl* PageNodeImpl::opener_frame_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return opener_frame_node_;
}
FrameNodeImpl* PageNodeImpl::embedder_frame_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return embedder_frame_node_;
}
FrameNodeImpl* PageNodeImpl::primary_main_frame_node() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (main_frame_nodes_.empty()) {
return nullptr;
}
for (FrameNodeImpl* frame : main_frame_nodes()) {
if (frame->IsActive() && !frame->parent_or_outer_document()) {
return frame;
}
}
return nullptr;
}
PageNode::NodeSetView<FrameNodeImpl*> PageNodeImpl::main_frame_nodes() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return NodeSetView<FrameNodeImpl*>(main_frame_nodes_);
}
void PageNodeImpl::SetOpenerFrameNode(FrameNodeImpl* opener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(opener);
DCHECK(graph()->NodeInGraph(opener));
DCHECK_NE(this, opener->page_node());
auto* previous_opener = opener_frame_node_.get();
if (previous_opener) {
previous_opener->RemoveOpenedPage(PassKey(), this);
}
opener_frame_node_ = opener;
opener->AddOpenedPage(PassKey(), this);
for (auto& observer : GetObservers()) {
observer.OnOpenerFrameNodeChanged(this, previous_opener);
}
}
void PageNodeImpl::ClearOpenerFrameNode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(nullptr, opener_frame_node_);
auto* previous_opener = opener_frame_node_.get();
opener_frame_node_->RemoveOpenedPage(PassKey(), this);
opener_frame_node_ = nullptr;
for (auto& observer : GetObservers()) {
observer.OnOpenerFrameNodeChanged(this, previous_opener);
}
}
void PageNodeImpl::SetEmbedderFrameNode(FrameNodeImpl* embedder) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(embedder);
DCHECK(graph()->NodeInGraph(embedder));
DCHECK_NE(this, embedder->page_node());
auto* previous_embedder = embedder_frame_node_.get();
if (previous_embedder) {
previous_embedder->RemoveEmbeddedPage(PassKey(), this);
}
embedder_frame_node_ = embedder;
embedder->AddEmbeddedPage(PassKey(), this);
for (auto& observer : GetObservers()) {
observer.OnEmbedderFrameNodeChanged(this, previous_embedder);
}
}
void PageNodeImpl::ClearEmbedderFrameNode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(nullptr, embedder_frame_node_);
auto* previous_embedder = embedder_frame_node_.get();
embedder_frame_node_->RemoveEmbeddedPage(PassKey(), this);
embedder_frame_node_ = nullptr;
for (auto& observer : GetObservers()) {
observer.OnEmbedderFrameNodeChanged(this, previous_embedder);
}
}
void PageNodeImpl::set_has_nonempty_beforeunload(
bool has_nonempty_beforeunload) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_nonempty_beforeunload_ = has_nonempty_beforeunload;
}
void PageNodeImpl::OnInitializingProperties() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NodeAttachedDataStorage::Create(this);
}
void PageNodeImpl::OnBeforeLeavingGraph() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Sever opener relationships.
if (opener_frame_node_) {
ClearOpenerFrameNode();
}
// Sever embedder relationships.
if (embedder_frame_node_) {
ClearEmbedderFrameNode();
}
DCHECK_EQ(0u, frame_node_count_);
}
void PageNodeImpl::CleanUpNodeState() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DestroyNodeInlineDataStorage();
}
const FrameNode* PageNodeImpl::GetOpenerFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || !opener_frame_node());
return opener_frame_node();
}
const FrameNode* PageNodeImpl::GetEmbedderFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || !embedder_frame_node());
return embedder_frame_node();
}
const FrameNode* PageNodeImpl::GetPrimaryMainFrameNode() const {
CHECK(graph()->NodeEdgesArePublic(this) || !primary_main_frame_node());
return primary_main_frame_node();
}
PageNode::NodeSetView<const FrameNode*> PageNodeImpl::GetMainFrameNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(graph()->NodeEdgesArePublic(this) || main_frame_nodes_.empty());
return NodeSetView<const FrameNode*>(main_frame_nodes_);
}
void PageNodeImpl::SetLifecycleState(LifecycleState lifecycle_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
lifecycle_state_.SetAndMaybeNotify(this, lifecycle_state);
}
void PageNodeImpl::SetIsHoldingWebLock(bool is_holding_weblock) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_holding_weblock_.SetAndMaybeNotify(this, is_holding_weblock);
}
void PageNodeImpl::SetIsHoldingBlockingIndexedDBLock(
bool is_holding_blocking_indexeddb_lock) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_holding_blocking_indexeddb_lock_.SetAndMaybeNotify(
this, is_holding_blocking_indexeddb_lock);
}
void PageNodeImpl::SetUsesWebRTC(bool uses_web_rtc) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
uses_web_rtc_.SetAndMaybeNotify(this, uses_web_rtc);
}
void PageNodeImpl::SetHadFormInteraction(bool had_form_interaction) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
had_form_interaction_.SetAndMaybeNotify(this, had_form_interaction);
}
void PageNodeImpl::SetHadUserEdits(bool had_user_edits) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
had_user_edits_.SetAndMaybeNotify(this, had_user_edits);
}
void PageNodeImpl::SetHasFreezingOriginTrialOptOut(
bool has_freezing_origin_trial_opt_out) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_freezing_origin_trial_opt_out_.SetAndMaybeNotify(
this, has_freezing_origin_trial_opt_out);
}
void PageNodeImpl::EmitMainFrameUrlChangedEvent(
const GURL& url,
std::optional<int64_t> navigation_id) const {
TRACE_EVENT_INSTANT("performance_manager.graph", "MainFrameUrlChanged",
frames_track_, [&](perfetto::EventContext& ctx) {
perfetto::protos::pbzero::PageLoad* page_load =
ctx.event<ChromeTrackEvent>()->set_page_load();
page_load->set_url(url.possibly_invalid_spec());
if (navigation_id.has_value()) {
page_load->set_navigation_id(navigation_id.value());
}
});
}
} // namespace performance_manager