diff --git a/chrome/browser/performance_manager/decorators/page_aggregator.cc b/chrome/browser/performance_manager/decorators/page_aggregator.cc index d9774e4b391d6a..adae9983c9d555 100644 --- a/chrome/browser/performance_manager/decorators/page_aggregator.cc +++ b/chrome/browser/performance_manager/decorators/page_aggregator.cc @@ -4,7 +4,7 @@ #include "chrome/browser/performance_manager/decorators/page_aggregator.h" -#include +#include #include "components/performance_manager/graph/node_attached_data_impl.h" #include "components/performance_manager/graph/page_node_impl.h" @@ -46,6 +46,10 @@ class PageAggregatorAccess { page_node->SetHadFormInteraction(base::PassKey(), value); } + + static void SetPageHadUserEdits(PageNodeImpl* page_node, bool value) { + page_node->SetHadUserEdits(base::PassKey(), value); + } }; // Specify the packing alignment for this class as it's expected to have a @@ -90,6 +94,14 @@ class PageAggregator::Data : public NodeAttachedDataImpl { PageNodeImpl* page_node, const FrameNode* frame_node_being_removed); + // Updates the counter of frames with user-initiated edits and sets the + // corresponding page-level property. |frame_node_being_removed| indicates + // if this function is called while removing a frame node. + void UpdateCurrentFrameCountForUserEdits( + bool frame_had_user_edits, + PageNodeImpl* page_node, + const FrameNode* frame_node_being_removed); + private: friend class PageAggregator; @@ -100,6 +112,9 @@ class PageAggregator::Data : public NodeAttachedDataImpl { // The number of current frames which have received some form interaction. uint32_t num_current_frames_with_form_interaction_ = 0; + + // The number of current frames which have some user-initiated edits. + uint32_t num_current_frames_with_user_edits_ = 0; }; #pragma pack(pop) @@ -160,6 +175,36 @@ void PageAggregator::Data::UpdateCurrentFrameCountForFormInteraction( page_node, num_current_frames_with_form_interaction_ > 0); } +void PageAggregator::Data::UpdateCurrentFrameCountForUserEdits( + bool frame_had_user_edits, + PageNodeImpl* page_node, + const FrameNode* frame_node_being_removed) { + if (frame_had_user_edits) { + ++num_current_frames_with_user_edits_; + } else { + DCHECK_GT(num_current_frames_with_user_edits_, 0U); + --num_current_frames_with_user_edits_; + } + // DCHECK that the |num_current_frames_with_user_edits_| accounting is + // correct. + DCHECK_EQ( + [&]() { + const auto frame_nodes = GraphOperations::GetFrameNodes(page_node); + size_t num_current_frames_with_user_edits = 0; + for (const auto* node : frame_nodes) { + if (node != frame_node_being_removed && node->IsCurrent() && + node->HadUserEdits()) { + ++num_current_frames_with_user_edits; + } + } + return num_current_frames_with_user_edits; + }(), + num_current_frames_with_user_edits_); + + PageAggregatorAccess::SetPageHadUserEdits( + page_node, num_current_frames_with_user_edits_ > 0); +} + PageAggregator::PageAggregator() = default; PageAggregator::~PageAggregator() = default; @@ -173,11 +218,15 @@ void PageAggregator::OnBeforeFrameNodeRemoved(const FrameNode* frame_node) { if (frame_node->IsCurrent()) { // Data should have been created when the frame became current. DCHECK(data); + // Decrement the form interaction and user edits counters for this page if + // needed. if (frame_node->HadFormInteraction()) { - // Decrement the form interaction counter for this page. data->UpdateCurrentFrameCountForFormInteraction(false, page_node, frame_node); } + if (frame_node->HadUserEdits()) { + data->UpdateCurrentFrameCountForUserEdits(false, page_node, frame_node); + } } // It is not guaranteed that the graph will be notified that the frame has @@ -192,8 +241,8 @@ void PageAggregator::OnIsCurrentChanged(const FrameNode* frame_node) { auto* page_node = PageNodeImpl::FromNode(frame_node->GetPageNode()); Data* data = Data::GetOrCreate(page_node); - // Check if the frame node had some form interaction, in this case there's two - // possibilities: + // Check if the frame node had some form interaction or user edit, in this + // case there's two possibilities: // - The frame became current: The counter of current frames with form // interactions should be increased. // - The frame became non current: The counter of current frames with form @@ -202,6 +251,10 @@ void PageAggregator::OnIsCurrentChanged(const FrameNode* frame_node) { data->UpdateCurrentFrameCountForFormInteraction(frame_node->IsCurrent(), page_node, nullptr); } + if (frame_node->HadUserEdits()) { + data->UpdateCurrentFrameCountForUserEdits(frame_node->IsCurrent(), + page_node, nullptr); + } } void PageAggregator::OnFrameIsHoldingWebLockChanged( @@ -229,6 +282,15 @@ void PageAggregator::OnHadFormInteractionChanged(const FrameNode* frame_node) { } } +void PageAggregator::OnHadUserEditsChanged(const FrameNode* frame_node) { + if (frame_node->IsCurrent()) { + auto* page_node = PageNodeImpl::FromNode(frame_node->GetPageNode()); + Data* data = Data::GetOrCreate(page_node); + data->UpdateCurrentFrameCountForUserEdits(frame_node->HadUserEdits(), + page_node, nullptr); + } +} + void PageAggregator::OnPassedToGraph(Graph* graph) { // This observer presumes that it's been added before any frame nodes exist in // the graph. diff --git a/chrome/browser/performance_manager/decorators/page_aggregator.h b/chrome/browser/performance_manager/decorators/page_aggregator.h index 0c6092f848b11f..042bd7697c48dc 100644 --- a/chrome/browser/performance_manager/decorators/page_aggregator.h +++ b/chrome/browser/performance_manager/decorators/page_aggregator.h @@ -39,6 +39,7 @@ class PageAggregator : public FrameNode::ObserverDefaultImpl, void OnFrameIsHoldingIndexedDBLockChanged( const FrameNode* frame_node) override; void OnHadFormInteractionChanged(const FrameNode* frame_node) override; + void OnHadUserEditsChanged(const FrameNode* frame_node) override; // GraphOwned implementation: void OnPassedToGraph(Graph* graph) override; diff --git a/chrome/browser/ui/webui/discards/graph_dump_impl.h b/chrome/browser/ui/webui/discards/graph_dump_impl.h index 39304d2d0b99de..07d14af0a0fa75 100644 --- a/chrome/browser/ui/webui/discards/graph_dump_impl.h +++ b/chrome/browser/ui/webui/discards/graph_dump_impl.h @@ -101,6 +101,9 @@ class DiscardsGraphDumpImpl : public discards::mojom::GraphDump, void OnHadFormInteractionChanged( const performance_manager::FrameNode* frame_node) override {} // Ignored. + void OnHadUserEditsChanged( + const performance_manager::FrameNode* frame_node) override {} + // Ignored. void OnIsAudibleChanged( const performance_manager::FrameNode* frame_node) override {} // Ignored. @@ -159,6 +162,9 @@ class DiscardsGraphDumpImpl : public discards::mojom::GraphDump, // Ignored. void OnHadFormInteractionChanged( const performance_manager::PageNode* page_node) override {} + // Ignored + void OnHadUserEditsChanged( + const performance_manager::PageNode* page_node) override {} // Ignored. void OnTitleUpdated(const performance_manager::PageNode* page_node) override { } diff --git a/components/performance_manager/graph/frame_node_impl.cc b/components/performance_manager/graph/frame_node_impl.cc index 2c01f9f96626f7..345a52987af351 100644 --- a/components/performance_manager/graph/frame_node_impl.cc +++ b/components/performance_manager/graph/frame_node_impl.cc @@ -93,6 +93,11 @@ void FrameNodeImpl::SetHadFormInteraction() { document_.had_form_interaction.SetAndMaybeNotify(this, true); } +void FrameNodeImpl::SetHadUserEdits() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + document_.had_user_edits.SetAndMaybeNotify(this, true); +} + void FrameNodeImpl::OnNonPersistentNotificationCreated() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); for (auto* observer : GetObservers()) @@ -228,6 +233,11 @@ bool FrameNodeImpl::had_form_interaction() const { return document_.had_form_interaction.value(); } +bool FrameNodeImpl::had_user_edits() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return document_.had_user_edits.value(); +} + bool FrameNodeImpl::is_audible() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return is_audible_.value(); @@ -577,6 +587,11 @@ bool FrameNodeImpl::HadFormInteraction() const { return had_form_interaction(); } +bool FrameNodeImpl::HadUserEdits() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return had_user_edits(); +} + bool FrameNodeImpl::IsAudible() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return is_audible(); @@ -781,6 +796,7 @@ void FrameNodeImpl::DocumentProperties::Reset(FrameNodeImpl* frame_node, // Network is busy on navigation. network_almost_idle.SetAndMaybeNotify(frame_node, false); had_form_interaction.SetAndMaybeNotify(frame_node, false); + had_user_edits.SetAndMaybeNotify(frame_node, false); } void FrameNodeImpl::OnWebMemoryMeasurementRequested( diff --git a/components/performance_manager/graph/frame_node_impl.h b/components/performance_manager/graph/frame_node_impl.h index 20ecbc0d5a9d66..8255bcdd9eef0e 100644 --- a/components/performance_manager/graph/frame_node_impl.h +++ b/components/performance_manager/graph/frame_node_impl.h @@ -67,6 +67,7 @@ class FrameNodeImpl void SetHasNonEmptyBeforeUnload(bool has_nonempty_beforeunload) override; void SetIsAdFrame(bool is_ad_frame) override; void SetHadFormInteraction() override; + void SetHadUserEdits() override; void OnNonPersistentNotificationCreated() override; void OnFirstContentfulPaint( base::TimeDelta time_since_navigation_start) override; @@ -103,6 +104,7 @@ class FrameNodeImpl const base::flat_set& child_worker_nodes() const; const PriorityAndReason& priority_and_reason() const; bool had_form_interaction() const; + bool had_user_edits() const; bool is_audible() const; const absl::optional& viewport_intersection() const; Visibility visibility() const; @@ -189,6 +191,7 @@ class FrameNodeImpl const WorkerNodeVisitor& visitor) const override; const PriorityAndReason& GetPriorityAndReason() const override; bool HadFormInteraction() const override; + bool HadUserEdits() const override; bool IsAudible() const override; const absl::optional& GetViewportIntersection() const override; Visibility GetVisibility() const override; @@ -218,10 +221,19 @@ class FrameNodeImpl network_almost_idle{false}; // Indicates if a form in the frame has been interacted with. + // TODO(crbug.com/1156388): Remove this once HadUserEdits is known to cover + // all existing cases. ObservedProperty::NotifiesOnlyOnChanges< bool, &FrameNodeObserver::OnHadFormInteractionChanged> had_form_interaction{false}; + + // Indicates that the user has made edits to the page. This is a superset of + // `had_form_interaction`, but can also represent changes to + // `contenteditable` elements. + ObservedProperty:: + NotifiesOnlyOnChanges + had_user_edits{false}; }; // Invoked by subframes on joining/leaving the graph. diff --git a/components/performance_manager/graph/frame_node_impl_describer.cc b/components/performance_manager/graph/frame_node_impl_describer.cc index 24c0965ce3166e..4783629495a95c 100644 --- a/components/performance_manager/graph/frame_node_impl_describer.cc +++ b/components/performance_manager/graph/frame_node_impl_describer.cc @@ -66,6 +66,7 @@ base::Value::Dict FrameNodeImplDescriber::DescribeFrameNodeData( impl->document_.has_nonempty_beforeunload); doc.Set("network_almost_idle", impl->document_.network_almost_idle.value()); doc.Set("had_form_interaction", impl->document_.had_form_interaction.value()); + ret.Set("had_user_edits", impl->document_.had_user_edits.value()); ret.Set("document", std::move(doc)); // Frame node properties. diff --git a/components/performance_manager/graph/frame_node_impl_unittest.cc b/components/performance_manager/graph/frame_node_impl_unittest.cc index 15a1bf407a9a22..3554696abc8cd9 100644 --- a/components/performance_manager/graph/frame_node_impl_unittest.cc +++ b/components/performance_manager/graph/frame_node_impl_unittest.cc @@ -148,6 +148,7 @@ class LenientMockObserver : public FrameNodeImpl::Observer { MOCK_METHOD2(OnPriorityAndReasonChanged, void(const FrameNode*, const PriorityAndReason& previous_value)); MOCK_METHOD1(OnHadFormInteractionChanged, void(const FrameNode*)); + MOCK_METHOD1(OnHadUserEditsChanged, void(const FrameNode*)); MOCK_METHOD1(OnIsAudibleChanged, void(const FrameNode*)); MOCK_METHOD1(OnViewportIntersectionChanged, void(const FrameNode*)); MOCK_METHOD2(OnFrameVisibilityChanged, @@ -406,6 +407,21 @@ TEST_F(FrameNodeImplTest, FormInteractions) { graph()->RemoveFrameNodeObserver(&obs); } +TEST_F(FrameNodeImplTest, UserEdits) { + auto process = CreateNode(); + auto page = CreateNode(); + auto frame_node = CreateFrameNodeAutoId(process.get(), page.get()); + + MockObserver obs; + graph()->AddFrameNodeObserver(&obs); + + EXPECT_CALL(obs, OnHadUserEditsChanged(frame_node.get())); + frame_node->SetHadUserEdits(); + EXPECT_TRUE(frame_node->had_user_edits()); + + graph()->RemoveFrameNodeObserver(&obs); +} + TEST_F(FrameNodeImplTest, IsAudible) { auto process = CreateNode(); auto page = CreateNode(); @@ -519,6 +535,7 @@ TEST_F(FrameNodeImplTest, PublicInterface) { public_frame_node->IsHoldingIndexedDBLock()); EXPECT_EQ(frame_node->had_form_interaction(), public_frame_node->HadFormInteraction()); + EXPECT_EQ(frame_node->had_user_edits(), public_frame_node->HadUserEdits()); // Use the child frame node to test the viewport intersection because the // viewport intersection of the main frame is not tracked. EXPECT_EQ(child_frame_node->viewport_intersection(), diff --git a/components/performance_manager/graph/page_node_impl.cc b/components/performance_manager/graph/page_node_impl.cc index 9c74768fc7aea6..403e004975b30b 100644 --- a/components/performance_manager/graph/page_node_impl.cc +++ b/components/performance_manager/graph/page_node_impl.cc @@ -314,6 +314,11 @@ bool PageNodeImpl::had_form_interaction() const { return had_form_interaction_.value(); } +bool PageNodeImpl::had_user_edits() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return had_user_edits_.value(); +} + const absl::optional& PageNodeImpl::freezing_vote() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -568,6 +573,11 @@ bool PageNodeImpl::HadFormInteraction() const { return had_form_interaction(); } +bool PageNodeImpl::HadUserEdits() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return had_user_edits(); +} + const WebContentsProxy& PageNodeImpl::GetContentsProxy() const { return contents_proxy(); } @@ -623,4 +633,9 @@ void PageNodeImpl::SetHadFormInteraction(bool had_form_interaction) { 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); +} + } // namespace performance_manager diff --git a/components/performance_manager/graph/page_node_impl.h b/components/performance_manager/graph/page_node_impl.h index 0b0e70c55cee2f..4657fcac3a2791 100644 --- a/components/performance_manager/graph/page_node_impl.h +++ b/components/performance_manager/graph/page_node_impl.h @@ -37,7 +37,7 @@ class PageNodeImpl using FrozenFrameDataStorage = InternalNodeAttachedDataStorage; using PageAggregatorDataStorage = - InternalNodeAttachedDataStorage; + InternalNodeAttachedDataStorage; static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kPage; } @@ -107,6 +107,7 @@ class PageNodeImpl int64_t navigation_id() const; const std::string& contents_mime_type() const; bool had_form_interaction() const; + bool had_user_edits() const; const absl::optional& freezing_vote() const; PageState page_state() const; @@ -142,6 +143,10 @@ class PageNodeImpl SetHadFormInteraction(had_form_interaction); } + void SetHadUserEditsForTesting(bool had_user_edits) { + SetHadUserEdits(had_user_edits); + } + base::WeakPtr GetWeakPtrOnUIThread() { // TODO(siggi): Validate thread context. return weak_this_; @@ -194,6 +199,11 @@ class PageNodeImpl SetHadFormInteraction(had_form_interaction); } + void SetHadUserEdits(base::PassKey, + bool had_user_edits) { + SetHadUserEdits(had_user_edits); + } + private: friend class PageNodeImplDescriber; @@ -220,6 +230,7 @@ class PageNodeImpl const base::flat_set GetMainFrameNodes() const override; const GURL& GetMainFrameUrl() const override; bool HadFormInteraction() const override; + bool HadUserEdits() const override; const WebContentsProxy& GetContentsProxy() const override; const absl::optional& GetFreezingVote() const override; @@ -235,6 +246,7 @@ class PageNodeImpl void SetIsHoldingWebLock(bool is_holding_weblock); void SetIsHoldingIndexedDBLock(bool is_holding_indexeddb_lock); void SetHadFormInteraction(bool had_form_interaction); + void SetHadUserEdits(bool had_user_edits); // The WebContentsProxy associated with this page. const WebContentsProxy contents_proxy_; @@ -354,6 +366,11 @@ class PageNodeImpl bool, &PageNodeObserver::OnHadFormInteractionChanged> had_form_interaction_ GUARDED_BY_CONTEXT(sequence_checker_){false}; + // Indicates if at least one frame of the page has received some + // user-initiated edits. + ObservedProperty:: + NotifiesOnlyOnChanges + had_user_edits_ GUARDED_BY_CONTEXT(sequence_checker_){false}; // The freezing vote associated with this page, see the comment of to // Page::GetFreezingVote for a description of the different values this can // take. diff --git a/components/performance_manager/graph/page_node_impl_describer.cc b/components/performance_manager/graph/page_node_impl_describer.cc index ba1593e3ef4d4d..833ed6df92731b 100644 --- a/components/performance_manager/graph/page_node_impl_describer.cc +++ b/components/performance_manager/graph/page_node_impl_describer.cc @@ -79,6 +79,7 @@ base::Value::Dict PageNodeImplDescriber::DescribePageNodeData( page_node_impl->is_holding_indexeddb_lock_.value()); result.Set("had_form_interaction", page_node_impl->had_form_interaction_.value()); + result.Set("had_user_edits", page_node_impl->had_user_edits_.value()); if (page_node_impl->embedding_type_ != PageNode::EmbeddingType::kInvalid) { result.Set("embedding_type", PageNode::ToString(page_node_impl->embedding_type_)); diff --git a/components/performance_manager/graph/page_node_impl_unittest.cc b/components/performance_manager/graph/page_node_impl_unittest.cc index 8ee98fb3a7a4dd..b4956b41d8701b 100644 --- a/components/performance_manager/graph/page_node_impl_unittest.cc +++ b/components/performance_manager/graph/page_node_impl_unittest.cc @@ -197,6 +197,20 @@ TEST_F(PageNodeImplTest, HadFormInteractions) { EXPECT_FALSE(page_node->had_form_interaction()); } +TEST_F(PageNodeImplTest, HadUserEdits) { + MockSinglePageInSingleProcessGraph mock_graph(graph()); + auto* page_node = mock_graph.page.get(); + + // This should be initialized to false. + EXPECT_FALSE(page_node->had_user_edits()); + + page_node->SetHadUserEditsForTesting(true); + EXPECT_TRUE(page_node->had_user_edits()); + + page_node->SetHadUserEditsForTesting(false); + EXPECT_FALSE(page_node->had_user_edits()); +} + TEST_F(PageNodeImplTest, GetFreezingVote) { MockSinglePageInSingleProcessGraph mock_graph(graph()); auto* page_node = mock_graph.page.get(); @@ -241,6 +255,7 @@ class LenientMockObserver : public PageNodeImpl::Observer { MOCK_METHOD1(OnTitleUpdated, void(const PageNode*)); MOCK_METHOD1(OnFaviconUpdated, void(const PageNode*)); MOCK_METHOD1(OnHadFormInteractionChanged, void(const PageNode*)); + MOCK_METHOD1(OnHadUserEditsChanged, void(const PageNode*)); MOCK_METHOD2(OnFreezingVoteChanged, void(const PageNode*, absl::optional)); MOCK_METHOD2(OnPageStateChanged, void(const PageNode*, PageNode::PageState)); diff --git a/components/performance_manager/public/graph/frame_node.h b/components/performance_manager/public/graph/frame_node.h index c7afa028d8fe46..53ea21c74558f1 100644 --- a/components/performance_manager/public/graph/frame_node.h +++ b/components/performance_manager/public/graph/frame_node.h @@ -203,6 +203,11 @@ class FrameNode : public Node { // Returns true if at least one form of the frame has been interacted with. virtual bool HadFormInteraction() const = 0; + // Returns true if the user has made edits to the page. This is a superset of + // `HadFormInteraction()` but also includes changes to `contenteditable` + // elements. + virtual bool HadUserEdits() const = 0; + // Returns true if the frame is audible, false otherwise. virtual bool IsAudible() const = 0; @@ -286,6 +291,12 @@ class FrameNodeObserver { // Called when the frame receives a form interaction. virtual void OnHadFormInteractionChanged(const FrameNode* frame_node) = 0; + // Called the first time the user has edited the content of an element. This + // is a superset of `OnHadFormInteractionChanged()`: form interactions trigger + // both events but changes to e.g. a `
` with the `contenteditable` + // property will only trigger `OnHadUserEditsChanged()`. + virtual void OnHadUserEditsChanged(const FrameNode* frame_node) = 0; + // Invoked when the IsAudible property changes. virtual void OnIsAudibleChanged(const FrameNode* frame_node) = 0; @@ -341,6 +352,7 @@ class FrameNode::ObserverDefaultImpl : public FrameNodeObserver { const FrameNode* frame_node, const PriorityAndReason& previous_value) override {} void OnHadFormInteractionChanged(const FrameNode* frame_node) override {} + void OnHadUserEditsChanged(const FrameNode* frame_node) override {} void OnIsAudibleChanged(const FrameNode* frame_node) override {} void OnViewportIntersectionChanged(const FrameNode* frame_node) override {} void OnFrameVisibilityChanged(const FrameNode* frame_node, diff --git a/components/performance_manager/public/graph/page_node.h b/components/performance_manager/public/graph/page_node.h index 1ba37b8d5312e0..5e3f88b6c6804b 100644 --- a/components/performance_manager/public/graph/page_node.h +++ b/components/performance_manager/public/graph/page_node.h @@ -199,6 +199,11 @@ class PageNode : public Node { // interactions. virtual bool HadFormInteraction() const = 0; + // Indicates if at least one of the frames in the page has received + // user-initiated edits. This is a superset of `HadFormInteraction()` that + // also includes changes to `contenteditable` elements. + virtual bool HadUserEdits() const = 0; + // Returns the web contents associated with this page node. It is valid to // call this function on any thread but the weak pointer must only be // dereferenced on the UI thread. @@ -304,6 +309,9 @@ class PageNodeObserver { // Invoked when the HadFormInteraction property changes. virtual void OnHadFormInteractionChanged(const PageNode* page_node) = 0; + // Invoked when the HadUserEdits property changes. + virtual void OnHadUserEditsChanged(const PageNode* page_node) = 0; + // Invoked when the page state changes. See `PageState` for the valid // transitions. virtual void OnPageStateChanged(const PageNode* page_node, @@ -368,6 +376,7 @@ class PageNode::ObserverDefaultImpl : public PageNodeObserver { void OnMainFrameUrlChanged(const PageNode* page_node) override {} void OnMainFrameDocumentChanged(const PageNode* page_node) override {} void OnHadFormInteractionChanged(const PageNode* page_node) override {} + void OnHadUserEditsChanged(const PageNode* page_node) override {} void OnTitleUpdated(const PageNode* page_node) override {} void OnFaviconUpdated(const PageNode* page_node) override {} void OnAboutToBeDiscarded(const PageNode* page_node, diff --git a/components/performance_manager/public/mojom/coordination_unit.mojom b/components/performance_manager/public/mojom/coordination_unit.mojom index 1c95927c129a63..d8a7afb9f26423 100644 --- a/components/performance_manager/public/mojom/coordination_unit.mojom +++ b/components/performance_manager/public/mojom/coordination_unit.mojom @@ -32,6 +32,12 @@ interface DocumentCoordinationUnit { // Called the first time a form in this document is interacted with. SetHadFormInteraction(); + // Called the first time the user has edited the content of an element. This + // is a superset of `SetHadFormInteraction()`: form interactions trigger both + // events but changes to e.g. a `
` with the `contenteditable` property + // will only trigger `SetHadUserEdits()`. + SetHadUserEdits(); + // Called whenever the frame associated with this document is tagged or // untagged as an ad, providing the new status. SetIsAdFrame(bool is_ad_frame); diff --git a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc index d5757118411ca6..21debf5afe8983 100644 --- a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc +++ b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc @@ -86,6 +86,7 @@ #include "third_party/blink/renderer/core/layout/line/inline_text_box.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" +#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h" namespace blink { @@ -2157,6 +2158,10 @@ void CompositeEditCommand::AppliedEditing() { } editor.RespondToChangedContents(new_selection.Base()); + + if (auto* rc = GetDocument().GetResourceCoordinator()) { + rc->SetHadUserEdits(); + } } } // namespace blink diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc index fbf5c367a7cf4e..90b602985912d9 100644 --- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc +++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.cc @@ -61,6 +61,16 @@ void DocumentResourceCoordinator::SetHadFormInteraction() { had_form_interaction_ = true; } +void DocumentResourceCoordinator::SetHadUserEdits() { + // Only send this signal for the first interaction as it doesn't get cleared + // for the lifetime of the frame and it's inefficient to send this message + // for every keystroke. + if (!had_user_edits_) { + service_->SetHadUserEdits(); + } + had_user_edits_ = true; +} + void DocumentResourceCoordinator::OnFirstContentfulPaint( base::TimeDelta time_since_navigation_start) { service_->OnFirstContentfulPaint(time_since_navigation_start); diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h index a48ef44b866156..5417c044ed4e08 100644 --- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h +++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h @@ -39,6 +39,7 @@ class PLATFORM_EXPORT DocumentResourceCoordinator final { void SetIsAdFrame(bool is_ad_frame); void OnNonPersistentNotificationCreated(); void SetHadFormInteraction(); + void SetHadUserEdits(); void OnFirstContentfulPaint(base::TimeDelta time_since_navigation_start); void OnWebMemoryMeasurementRequested( WebMemoryMeasurementMode mode, @@ -51,6 +52,7 @@ class PLATFORM_EXPORT DocumentResourceCoordinator final { service_; bool had_form_interaction_ = false; + bool had_user_edits_ = false; }; } // namespace blink