Commit b1a33e63 authored by Liquan(Max) Gu's avatar Liquan(Max) Gu Committed by Commit Bot

[FCP++] Text: Replace node_id with LayoutObject* - part 1

FCP++ is currently using node_id to track a layout object. But
we should use LayoutObject* instead to track the nodes. The replacement
will be more efficient and avoid the generation of node id.

After we use layout object* in text record, we start to get a risk of
UAF. UAF happens when we stop recording, block the LayoutObject
removal hook by is_recording_, and use LayoutObject* in
PopulateTraceValue. The fix to it is to clear up everything

We also noticed a risk of using |this| in two callback functions, but
it proved to be not a real risk in crbug.com/976357

|cached_largest_paint_candidate_| was raw pointer. It's better to be
a WeakPtr so no risk of becoming a dead pointer.

Note that this CL is mainly to replace node_id with LayoutObject* in
TextPaintTimingDetector's sets & maps. A subsequent patch will be made
to replace node with LayoutObject in the control logic of
TextPaintTimingDetector. And do the same thing for
ImagePaintTimingDetector.

Bug:967837

Change-Id: Iba0496cfca3f5056724c3267ec10226913d4e70e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1649467
Commit-Queue: Liquan (Max) Gu <maxlg@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#671360}
parent 6d5a1553
...@@ -3156,7 +3156,7 @@ void LayoutObject::WillBeRemovedFromTree() { ...@@ -3156,7 +3156,7 @@ void LayoutObject::WillBeRemovedFromTree() {
if (RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled() || if (RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled() ||
RuntimeEnabledFeatures::ElementTimingEnabled(&GetDocument())) { RuntimeEnabledFeatures::ElementTimingEnabled(&GetDocument())) {
if (LocalFrameView* frame_view = GetFrameView()) { if (LocalFrameView* frame_view = GetFrameView()) {
frame_view->GetPaintTimingDetector().NotifyNodeRemoved(*this); frame_view->GetPaintTimingDetector().LayoutObjectWillBeDestroyed(*this);
} }
} }
} }
......
...@@ -153,7 +153,7 @@ void ImagePaintTimingDetector::OnPaintFinished() { ...@@ -153,7 +153,7 @@ void ImagePaintTimingDetector::OnPaintFinished() {
RegisterNotifySwapTime(); RegisterNotifySwapTime();
} }
void ImagePaintTimingDetector::NotifyNodeRemoved(DOMNodeId node_id) { void ImagePaintTimingDetector::LayoutObjectWillBeDestroyed(DOMNodeId node_id) {
if (!is_recording_) if (!is_recording_)
return; return;
// Todo: check whether it is visible background image. // Todo: check whether it is visible background image.
......
...@@ -212,7 +212,7 @@ class CORE_EXPORT ImagePaintTimingDetector final ...@@ -212,7 +212,7 @@ class CORE_EXPORT ImagePaintTimingDetector final
const ImageResourceContent& cached_image, const ImageResourceContent& cached_image,
const PropertyTreeState& current_paint_chunk_properties); const PropertyTreeState& current_paint_chunk_properties);
void OnPaintFinished(); void OnPaintFinished();
void NotifyNodeRemoved(DOMNodeId); void LayoutObjectWillBeDestroyed(DOMNodeId);
void NotifyBackgroundImageRemoved(DOMNodeId, const ImageResourceContent*); void NotifyBackgroundImageRemoved(DOMNodeId, const ImageResourceContent*);
// After the method being called, the detector stops to record new entries and // After the method being called, the detector stops to record new entries and
// node removal. But it still observe the loading status. In other words, if // node removal. But it still observe the loading status. In other words, if
......
...@@ -31,11 +31,11 @@ void LargestContentfulPaintCalculator::OnLargestImageUpdated( ...@@ -31,11 +31,11 @@ void LargestContentfulPaintCalculator::OnLargestImageUpdated(
} }
void LargestContentfulPaintCalculator::OnLargestTextUpdated( void LargestContentfulPaintCalculator::OnLargestTextUpdated(
const TextRecord* largest_text) { base::WeakPtr<TextRecord> largest_text) {
largest_text_.reset(); largest_text_.reset();
if (largest_text) { if (largest_text) {
largest_text_ = std::make_unique<TextRecord>( largest_text_ = std::make_unique<TextRecord>(
kInvalidDOMNodeId, largest_text->first_size, FloatRect()); largest_text->node_id, largest_text->first_size, FloatRect());
largest_text_->paint_time = largest_text->paint_time; largest_text_->paint_time = largest_text->paint_time;
} }
......
...@@ -21,7 +21,7 @@ class CORE_EXPORT LargestContentfulPaintCalculator final ...@@ -21,7 +21,7 @@ class CORE_EXPORT LargestContentfulPaintCalculator final
void OnLargestImageUpdated(const ImageRecord* largest_image); void OnLargestImageUpdated(const ImageRecord* largest_image);
void OnLargestTextUpdated(const TextRecord* largest_text); void OnLargestTextUpdated(base::WeakPtr<TextRecord> largest_text);
void Trace(blink::Visitor* visitor); void Trace(blink::Visitor* visitor);
......
...@@ -61,8 +61,10 @@ PaintTimingDetector::PaintTimingDetector(LocalFrameView* frame_view) ...@@ -61,8 +61,10 @@ PaintTimingDetector::PaintTimingDetector(LocalFrameView* frame_view)
void PaintTimingDetector::NotifyPaintFinished() { void PaintTimingDetector::NotifyPaintFinished() {
if (text_paint_timing_detector_) { if (text_paint_timing_detector_) {
text_paint_timing_detector_->OnPaintFinished(); text_paint_timing_detector_->OnPaintFinished();
if (text_paint_timing_detector_->FinishedReportingText()) if (text_paint_timing_detector_->FinishedReportingText()) {
text_paint_timing_detector_->StopRecordEntries();
text_paint_timing_detector_ = nullptr; text_paint_timing_detector_ = nullptr;
}
} }
if (image_paint_timing_detector_) { if (image_paint_timing_detector_) {
image_paint_timing_detector_->OnPaintFinished(); image_paint_timing_detector_->OnPaintFinished();
...@@ -114,15 +116,17 @@ void PaintTimingDetector::NotifyImagePaint( ...@@ -114,15 +116,17 @@ void PaintTimingDetector::NotifyImagePaint(
} }
} }
void PaintTimingDetector::NotifyNodeRemoved(const LayoutObject& object) { void PaintTimingDetector::LayoutObjectWillBeDestroyed(
const LayoutObject& object) {
if (text_paint_timing_detector_)
text_paint_timing_detector_->LayoutObjectWillBeDestroyed(object);
DOMNodeId node_id = DOMNodeIds::ExistingIdForNode(object.GetNode()); DOMNodeId node_id = DOMNodeIds::ExistingIdForNode(object.GetNode());
if (node_id == kInvalidDOMNodeId) if (node_id == kInvalidDOMNodeId)
return; return;
if (text_paint_timing_detector_)
text_paint_timing_detector_->NotifyNodeRemoved(node_id);
if (image_paint_timing_detector_) if (image_paint_timing_detector_)
image_paint_timing_detector_->NotifyNodeRemoved(node_id); image_paint_timing_detector_->LayoutObjectWillBeDestroyed(node_id);
} }
void PaintTimingDetector::NotifyBackgroundImageRemoved( void PaintTimingDetector::NotifyBackgroundImageRemoved(
...@@ -137,12 +141,7 @@ void PaintTimingDetector::NotifyBackgroundImageRemoved( ...@@ -137,12 +141,7 @@ void PaintTimingDetector::NotifyBackgroundImageRemoved(
} }
} }
void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) { void PaintTimingDetector::StopRecordingIfNeeded() {
if (type == WebInputEvent::kMouseMove || type == WebInputEvent::kMouseEnter ||
type == WebInputEvent::kMouseLeave ||
WebInputEvent::IsPinchGestureEventType(type)) {
return;
}
DCHECK(frame_view_); DCHECK(frame_view_);
if (text_paint_timing_detector_) { if (text_paint_timing_detector_) {
text_paint_timing_detector_->StopRecordingLargestTextPaint(); text_paint_timing_detector_->StopRecordingLargestTextPaint();
...@@ -155,19 +154,19 @@ void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) { ...@@ -155,19 +154,19 @@ void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) {
image_paint_timing_detector_->StopRecordEntries(); image_paint_timing_detector_->StopRecordEntries();
} }
void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) {
if (type == WebInputEvent::kMouseMove || type == WebInputEvent::kMouseEnter ||
type == WebInputEvent::kMouseLeave ||
WebInputEvent::IsPinchGestureEventType(type)) {
return;
}
StopRecordingIfNeeded();
}
void PaintTimingDetector::NotifyScroll(ScrollType scroll_type) { void PaintTimingDetector::NotifyScroll(ScrollType scroll_type) {
if (scroll_type != kUserScroll && scroll_type != kCompositorScroll) if (scroll_type != kUserScroll && scroll_type != kCompositorScroll)
return; return;
DCHECK(frame_view_); StopRecordingIfNeeded();
if (text_paint_timing_detector_) {
text_paint_timing_detector_->StopRecordingLargestTextPaint();
if (!RuntimeEnabledFeatures::ElementTimingEnabled(
frame_view_->GetFrame().GetDocument())) {
text_paint_timing_detector_->StopRecordEntries();
}
}
if (image_paint_timing_detector_)
image_paint_timing_detector_->StopRecordEntries();
} }
bool PaintTimingDetector::NeedToNotifyInputOrScroll() const { bool PaintTimingDetector::NeedToNotifyInputOrScroll() const {
......
...@@ -49,7 +49,7 @@ class CORE_EXPORT PaintTimingDetector ...@@ -49,7 +49,7 @@ class CORE_EXPORT PaintTimingDetector
const PropertyTreeState& current_paint_chunk_properties); const PropertyTreeState& current_paint_chunk_properties);
inline static void NotifyTextPaint(const IntRect& text_visual_rect); inline static void NotifyTextPaint(const IntRect& text_visual_rect);
void NotifyNodeRemoved(const LayoutObject&); void LayoutObjectWillBeDestroyed(const LayoutObject&);
void NotifyBackgroundImageRemoved(const LayoutObject&, void NotifyBackgroundImageRemoved(const LayoutObject&,
const ImageResourceContent*); const ImageResourceContent*);
void NotifyPaintFinished(); void NotifyPaintFinished();
...@@ -83,6 +83,7 @@ class CORE_EXPORT PaintTimingDetector ...@@ -83,6 +83,7 @@ class CORE_EXPORT PaintTimingDetector
void Trace(Visitor* visitor); void Trace(Visitor* visitor);
private: private:
void StopRecordingIfNeeded();
bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const; bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const;
bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const; bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const;
Member<LocalFrameView> frame_view_; Member<LocalFrameView> frame_view_;
......
...@@ -40,9 +40,6 @@ class TextRecord : public base::SupportsWeakPtr<TextRecord> { ...@@ -40,9 +40,6 @@ class TextRecord : public base::SupportsWeakPtr<TextRecord> {
FloatRect element_timing_rect_; FloatRect element_timing_rect_;
// The time of the first paint after fully loaded. // The time of the first paint after fully loaded.
base::TimeTicks paint_time = base::TimeTicks(); base::TimeTicks paint_time = base::TimeTicks();
#ifndef NDEBUG
String text = "";
#endif
DISALLOW_COPY_AND_ASSIGN(TextRecord); DISALLOW_COPY_AND_ASSIGN(TextRecord);
}; };
...@@ -56,40 +53,43 @@ class TextRecordsManager { ...@@ -56,40 +53,43 @@ class TextRecordsManager {
public: public:
TextRecordsManager(); TextRecordsManager();
TextRecord* FindLargestPaintCandidate(); base::WeakPtr<TextRecord> FindLargestPaintCandidate();
void RemoveVisibleRecord(const DOMNodeId&); void RemoveVisibleRecord(const LayoutObject&);
void RemoveInvisibleRecord(const DOMNodeId&); void RemoveInvisibleRecord(const LayoutObject&);
inline void RecordInvisibleNode(const DOMNodeId& node_id) { inline void RecordInvisibleObject(const LayoutObject& object) {
DCHECK(!HasTooManyNodes()); DCHECK(!HasTooManyNodes());
invisible_node_ids_.insert(node_id); invisible_node_ids_.insert(&object);
} }
void RecordVisibleNode(const DOMNodeId&, void RecordVisibleObject(const LayoutObject&,
const uint64_t& visual_size, const uint64_t& visual_size,
const FloatRect& element_timing_rect, const FloatRect& element_timing_rect,
const LayoutObject&); DOMNodeId);
bool NeedMeausuringPaintTime() const { bool NeedMeausuringPaintTime() const {
return !texts_queued_for_paint_time_.IsEmpty(); return !texts_queued_for_paint_time_.IsEmpty();
} }
void AssignPaintTimeToQueuedNodes(const base::TimeTicks&); void AssignPaintTimeToQueuedNodes(const base::TimeTicks&);
bool HasTooManyNodes() const; bool HasTooManyNodes() const;
inline bool HasRecorded(const DOMNodeId& node_id) const { inline bool HasRecorded(const LayoutObject& object) const {
return visible_node_map_.Contains(node_id) || return visible_node_map_.Contains(&object) ||
invisible_node_ids_.Contains(node_id); invisible_node_ids_.Contains(&object);
} }
size_t CountVisibleNodes() const { return visible_node_map_.size(); } size_t CountVisibleObjects() const { return visible_node_map_.size(); }
size_t CountInvisibleNodes() const { return invisible_node_ids_.size(); } size_t CountInvisibleObjects() const { return invisible_node_ids_.size(); }
inline bool IsKnownVisibleNode(const DOMNodeId& node_id) const { inline bool IsKnownVisible(const LayoutObject& object) const {
return visible_node_map_.Contains(node_id); return visible_node_map_.Contains(&object);
} }
inline bool IsKnownInvisibleNode(const DOMNodeId& node_id) const { inline bool IsKnownInvisible(const LayoutObject& object) const {
return invisible_node_ids_.Contains(node_id); return invisible_node_ids_.Contains(&object);
} }
void CleanUp();
void CleanUpLargestContentfulPaint();
void StopRecordingLargestTextPaint(); void StopRecordingLargestTextPaint();
bool IsRecordingLargestTextPaint() const { return is_recording_ltp_; } bool IsRecordingLargestTextPaint() const { return is_recording_ltp_; }
...@@ -113,14 +113,15 @@ class TextRecordsManager { ...@@ -113,14 +113,15 @@ class TextRecordsManager {
// Timing. // Timing.
bool is_recording_ltp_ = bool is_recording_ltp_ =
RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled(); RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled();
HashMap<DOMNodeId, std::unique_ptr<TextRecord>> visible_node_map_; // Once LayoutObject* is destroyed, |visible_node_map_| and
HashSet<DOMNodeId> invisible_node_ids_; // |invisible_node_ids_| must immediately clear the corresponding record from
// This is used to order the nodes in |visible_node_map_| so that we can find // themselves.
// the largest node efficiently. Note that the entries in |size_ordered_set_| HashMap<const LayoutObject*, std::unique_ptr<TextRecord>> visible_node_map_;
// and |visible_node_map_| should always be added/deleted together. HashSet<const LayoutObject*> invisible_node_ids_;
TextRecordSet size_ordered_set_; TextRecordSet size_ordered_set_;
Deque<base::WeakPtr<TextRecord>> texts_queued_for_paint_time_; Deque<base::WeakPtr<TextRecord>> texts_queued_for_paint_time_;
TextRecord* cached_largest_paint_candidate_; base::WeakPtr<TextRecord> cached_largest_paint_candidate_;
Member<TextElementTiming> text_element_timing_; Member<TextElementTiming> text_element_timing_;
DISALLOW_COPY_AND_ASSIGN(TextRecordsManager); DISALLOW_COPY_AND_ASSIGN(TextRecordsManager);
...@@ -156,8 +157,8 @@ class CORE_EXPORT TextPaintTimingDetector final ...@@ -156,8 +157,8 @@ class CORE_EXPORT TextPaintTimingDetector final
const IntRect& aggregated_visual_rect, const IntRect& aggregated_visual_rect,
const PropertyTreeState&); const PropertyTreeState&);
void OnPaintFinished(); void OnPaintFinished();
void NotifyNodeRemoved(DOMNodeId); void LayoutObjectWillBeDestroyed(const LayoutObject&);
TextRecord* FindLargestPaintCandidate(); base::WeakPtr<TextRecord> FindLargestPaintCandidate();
void StopRecordEntries(); void StopRecordEntries();
void StopRecordingLargestTextPaint(); void StopRecordingLargestTextPaint();
bool IsRecording() const { return is_recording_; } bool IsRecording() const { return is_recording_; }
......
...@@ -42,16 +42,17 @@ class TextPaintTimingDetectorTest ...@@ -42,16 +42,17 @@ class TextPaintTimingDetectorTest
LocalFrameView& GetChildFrameView() { return *ChildFrame().View(); } LocalFrameView& GetChildFrameView() { return *ChildFrame().View(); }
unsigned CountVisibleTexts() { TextPaintTimingDetector* GetTextPaintTimingDetector() {
if (!GetPaintTimingDetector().GetTextPaintTimingDetector()) return GetPaintTimingDetector().GetTextPaintTimingDetector();
return 0u; }
return GetPaintTimingDetector() wtf_size_t CountVisibleTexts() {
.GetTextPaintTimingDetector() DCHECK(GetTextPaintTimingDetector());
return GetTextPaintTimingDetector()
->records_manager_.visible_node_map_.size(); ->records_manager_.visible_node_map_.size();
} }
unsigned CountRankingSetSize() { wtf_size_t CountRankingSetSize() {
return GetPaintTimingDetector() return GetPaintTimingDetector()
.GetTextPaintTimingDetector() .GetTextPaintTimingDetector()
->records_manager_.size_ordered_set_.size(); ->records_manager_.size_ordered_set_.size();
...@@ -118,14 +119,14 @@ class TextPaintTimingDetectorTest ...@@ -118,14 +119,14 @@ class TextPaintTimingDetectorTest
return div; return div;
} }
TextRecord* TextRecordOfLargestTextPaint() { base::WeakPtr<TextRecord> TextRecordOfLargestTextPaint() {
return GetFrameView() return GetFrameView()
.GetPaintTimingDetector() .GetPaintTimingDetector()
.GetTextPaintTimingDetector() .GetTextPaintTimingDetector()
->FindLargestPaintCandidate(); ->FindLargestPaintCandidate();
} }
TextRecord* ChildFrameTextRecordOfLargestTextPaint() { base::WeakPtr<TextRecord> ChildFrameTextRecordOfLargestTextPaint() {
return GetChildFrameView() return GetChildFrameView()
.GetPaintTimingDetector() .GetPaintTimingDetector()
.GetTextPaintTimingDetector() .GetTextPaintTimingDetector()
...@@ -282,19 +283,19 @@ TEST_F(TextPaintTimingDetectorTest, VisitSameNodeTwiceBeforePaintTimeIsSet) { ...@@ -282,19 +283,19 @@ TEST_F(TextPaintTimingDetectorTest, VisitSameNodeTwiceBeforePaintTimeIsSet) {
TEST_F(TextPaintTimingDetectorTest, LargestTextPaint_ReportFirstPaintTime) { TEST_F(TextPaintTimingDetectorTest, LargestTextPaint_ReportFirstPaintTime) {
base::TimeTicks start_time = NowTicks(); base::TimeTicks start_time = NowTicks();
AdvanceClock(base::TimeDelta::FromSecondsD(1)); AdvanceClock(TimeDelta::FromSecondsD(1));
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
)HTML"); )HTML");
Element* text = AppendDivElementToBody("text"); Element* text = AppendDivElementToBody("text");
UpdateAllLifecyclePhasesAndSimulateSwapTime(); UpdateAllLifecyclePhasesAndSimulateSwapTime();
AdvanceClock(base::TimeDelta::FromSecondsD(1)); AdvanceClock(TimeDelta::FromSecondsD(1));
text->setAttribute(html_names::kStyleAttr, text->setAttribute(html_names::kStyleAttr,
AtomicString("position:fixed;left:30px")); AtomicString("position:fixed;left:30px"));
UpdateAllLifecyclePhasesAndSimulateSwapTime(); UpdateAllLifecyclePhasesAndSimulateSwapTime();
AdvanceClock(base::TimeDelta::FromSecondsD(1)); AdvanceClock(TimeDelta::FromSecondsD(1));
TextRecord* record = TextRecordOfLargestTextPaint(); base::WeakPtr<TextRecord> record = TextRecordOfLargestTextPaint();
EXPECT_TRUE(record); EXPECT_TRUE(record);
EXPECT_EQ(record->paint_time, start_time + base::TimeDelta::FromSecondsD(1)); EXPECT_EQ(record->paint_time, start_time + TimeDelta::FromSecondsD(1));
} }
TEST_F(TextPaintTimingDetectorTest, TEST_F(TextPaintTimingDetectorTest,
...@@ -438,11 +439,7 @@ TEST_F(TextPaintTimingDetectorTest, StopRecordingOverNodeLimit) { ...@@ -438,11 +439,7 @@ TEST_F(TextPaintTimingDetectorTest, StopRecordingOverNodeLimit) {
AppendDivElementToBody(WTF::String::Number(5000)); AppendDivElementToBody(WTF::String::Number(5000));
UpdateAllLifecyclePhasesAndSimulateSwapTime(); UpdateAllLifecyclePhasesAndSimulateSwapTime();
// Reached limit, so stopped recording and now should have 0 texts. // Reached limit, so stopped recording and now should have 0 texts.
EXPECT_EQ(CountVisibleTexts(), 0u); EXPECT_FALSE(GetTextPaintTimingDetector());
AppendDivElementToBody(WTF::String::Number(5001));
UpdateAllLifecyclePhasesAndSimulateSwapTime();
EXPECT_EQ(CountVisibleTexts(), 0u);
} }
// This is for comparison with the ClippedByViewport test. // This is for comparison with the ClippedByViewport test.
...@@ -522,7 +519,7 @@ TEST_F(TextPaintTimingDetectorTest, Iframe) { ...@@ -522,7 +519,7 @@ TEST_F(TextPaintTimingDetectorTest, Iframe) {
UpdateAllLifecyclePhasesForTest(); UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(CountPendingSwapTime(GetChildFrameView()), 1u); EXPECT_EQ(CountPendingSwapTime(GetChildFrameView()), 1u);
ChildFrameSwapTimeCallBack(); ChildFrameSwapTimeCallBack();
TextRecord* text = ChildFrameTextRecordOfLargestTextPaint(); base::WeakPtr<TextRecord> text = ChildFrameTextRecordOfLargestTextPaint();
EXPECT_TRUE(text); EXPECT_TRUE(text);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment