0

ContentCapture: Get the visual rect from cc

Currently to get the visual rect from layout tree requires document
being paint clean, it became not a good way for ContentCapture task
to get the visual rect now.

This patch get the visual rect from cc while capturing the content.
It should have better performance than from layout tree.

Bug: 1126615,1122472


Change-Id: Id10fa1ad84f64058f30c3523b78d2aff822e62a1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2388344
Reviewed-by: Philip Rogers <pdr@chromium.org>
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Commit-Queue: Tao Bai <michaelbai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805934}
This commit is contained in:
Michael Bai
2020-09-10 22:06:48 +00:00
committed by Commit Bot
parent 4f1ee370d1
commit c57702478b
24 changed files with 278 additions and 104 deletions

@ -11,6 +11,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <map> #include <map>
#include <utility>
#include <vector> #include <vector>
#include "base/check_op.h" #include "base/check_op.h"
@ -70,7 +71,9 @@ class RTree {
// Given a query rect, returns elements that intersect the rect. Elements are // Given a query rect, returns elements that intersect the rect. Elements are
// returned in the order they appeared in the initial container. // returned in the order they appeared in the initial container.
void Search(const gfx::Rect& query, std::vector<T>* results) const; void Search(const gfx::Rect& query,
std::vector<T>* results,
std::vector<gfx::Rect>* rects = nullptr) const;
// Given a query rect, returns non-owning pointers to elements that intersect // Given a query rect, returns non-owning pointers to elements that intersect
// the rect. Elements are returned in the order they appeared in the initial // the rect. Elements are returned in the order they appeared in the initial
@ -123,7 +126,8 @@ class RTree {
void SearchRecursive(Node<T>* root, void SearchRecursive(Node<T>* root,
const gfx::Rect& query, const gfx::Rect& query,
std::vector<T>* results) const; std::vector<T>* results,
std::vector<gfx::Rect>* rects = nullptr) const;
void SearchRefsRecursive(Node<T>* root, void SearchRefsRecursive(Node<T>* root,
const gfx::Rect& query, const gfx::Rect& query,
std::vector<const T*>* results) const; std::vector<const T*>* results) const;
@ -132,7 +136,8 @@ class RTree {
// and SearchRefsRecursive for when !has_valid_bounds(). // and SearchRefsRecursive for when !has_valid_bounds().
void SearchRecursiveFallback(Node<T>* root, void SearchRecursiveFallback(Node<T>* root,
const gfx::Rect& query, const gfx::Rect& query,
std::vector<T>* results) const; std::vector<T>* results,
std::vector<gfx::Rect>* rects = nullptr) const;
void SearchRefsRecursiveFallback(Node<T>* root, void SearchRefsRecursiveFallback(Node<T>* root,
const gfx::Rect& query, const gfx::Rect& query,
std::vector<const T*>* results) const; std::vector<const T*>* results) const;
@ -307,14 +312,16 @@ auto RTree<T>::BuildRecursive(std::vector<Branch<T>>* branches, int level)
} }
template <typename T> template <typename T>
void RTree<T>::Search(const gfx::Rect& query, std::vector<T>* results) const { void RTree<T>::Search(const gfx::Rect& query,
std::vector<T>* results,
std::vector<gfx::Rect>* rects) const {
results->clear(); results->clear();
if (num_data_elements_ == 0) if (num_data_elements_ == 0)
return; return;
if (!has_valid_bounds_) { if (!has_valid_bounds_) {
SearchRecursiveFallback(root_.subtree, query, results); SearchRecursiveFallback(root_.subtree, query, results, rects);
} else if (query.Intersects(root_.bounds)) { } else if (query.Intersects(root_.bounds)) {
SearchRecursive(root_.subtree, query, results); SearchRecursive(root_.subtree, query, results, rects);
} }
} }
@ -334,13 +341,17 @@ void RTree<T>::SearchRefs(const gfx::Rect& query,
template <typename T> template <typename T>
void RTree<T>::SearchRecursive(Node<T>* node, void RTree<T>::SearchRecursive(Node<T>* node,
const gfx::Rect& query, const gfx::Rect& query,
std::vector<T>* results) const { std::vector<T>* results,
std::vector<gfx::Rect>* rects) const {
for (uint16_t i = 0; i < node->num_children; ++i) { for (uint16_t i = 0; i < node->num_children; ++i) {
if (query.Intersects(node->children[i].bounds)) { if (query.Intersects(node->children[i].bounds)) {
if (node->level == 0) if (node->level == 0) {
results->push_back(node->children[i].payload); results->push_back(node->children[i].payload);
else if (rects)
SearchRecursive(node->children[i].subtree, query, results); rects->push_back(node->children[i].bounds);
} else {
SearchRecursive(node->children[i].subtree, query, results, rects);
}
} }
} }
} }
@ -364,13 +375,17 @@ void RTree<T>::SearchRefsRecursive(Node<T>* node,
template <typename T> template <typename T>
void RTree<T>::SearchRecursiveFallback(Node<T>* node, void RTree<T>::SearchRecursiveFallback(Node<T>* node,
const gfx::Rect& query, const gfx::Rect& query,
std::vector<T>* results) const { std::vector<T>* results,
std::vector<gfx::Rect>* rects) const {
for (uint16_t i = 0; i < node->num_children; ++i) { for (uint16_t i = 0; i < node->num_children; ++i) {
if (node->level == 0) { if (node->level == 0) {
if (query.Intersects(node->children[i].bounds)) if (query.Intersects(node->children[i].bounds)) {
results->push_back(node->children[i].payload); results->push_back(node->children[i].payload);
if (rects)
rects->push_back(node->children[i].bounds);
}
} else { } else {
SearchRecursive(node->children[i].subtree, query, results); SearchRecursive(node->children[i].subtree, query, results, rects);
} }
} }
} }

@ -5,6 +5,7 @@
#include "cc/base/rtree.h" #include "cc/base/rtree.h"
#include <stddef.h> #include <stddef.h>
#include <utility>
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
@ -26,6 +27,18 @@ void SearchAndVerifyRefs(const RTree<T>& rtree,
EXPECT_EQ(*ref_results[i], (*results)[i]); EXPECT_EQ(*ref_results[i], (*results)[i]);
} }
} }
template <typename T>
void SearchAndVerifyBounds(const RTree<T>& rtree,
const gfx::Rect& query,
std::vector<T>* results,
std::vector<gfx::Rect>* rects) {
rtree.Search(query, results, rects);
ASSERT_EQ(results->size(), rects->size());
for (auto& rect : *rects) {
EXPECT_TRUE(rect.Intersects(query));
}
}
} // namespace } // namespace
TEST(RTreeTest, ReserveNodesDoesntDcheck) { TEST(RTreeTest, ReserveNodesDoesntDcheck) {
@ -220,6 +233,14 @@ TEST(RTreeTest, Payload) {
ASSERT_EQ(1u, results.size()); ASSERT_EQ(1u, results.size());
EXPECT_FLOAT_EQ(10.f, results[0]); EXPECT_FLOAT_EQ(10.f, results[0]);
// Search with bounds
std::vector<gfx::Rect> rects;
SearchAndVerifyBounds(rtree, gfx::Rect(0, 0, 1, 1), &results, &rects);
ASSERT_EQ(1u, results.size());
ASSERT_EQ(results.size(), rects.size());
EXPECT_FLOAT_EQ(10.f, results[0]);
EXPECT_EQ(gfx::Rect(0, 0, 10, 10), rects[0]);
SearchAndVerifyRefs(rtree, gfx::Rect(5, 5, 10, 10), &results); SearchAndVerifyRefs(rtree, gfx::Rect(5, 5, 10, 10), &results);
ASSERT_EQ(4u, results.size()); ASSERT_EQ(4u, results.size());
// Items returned should be in the order they were inserted. // Items returned should be in the order they were inserted.

@ -242,7 +242,7 @@ bool Layer::IsPropertyChangeAllowed() const {
} }
void Layer::CaptureContent(const gfx::Rect& rect, void Layer::CaptureContent(const gfx::Rect& rect,
std::vector<NodeId>* content) {} std::vector<NodeInfo>* content) {}
sk_sp<SkPicture> Layer::GetPicture() const { sk_sp<SkPicture> Layer::GetPicture() const {
return nullptr; return nullptr;

@ -570,9 +570,9 @@ class CC_EXPORT Layer : public base::RefCounted<Layer> {
void ShowScrollbars() { needs_show_scrollbars_ = true; } void ShowScrollbars() { needs_show_scrollbars_ = true; }
// Captures text content within the given |rect| and returns the associated // Captures text content within the given |rect| and returns the associated
// NodeId in |content|. // NodeInfo in |content|.
virtual void CaptureContent(const gfx::Rect& rect, virtual void CaptureContent(const gfx::Rect& rect,
std::vector<NodeId>* content); std::vector<NodeInfo>* content);
// For tracing. Gets a recorded rasterization of this layer's contents that // For tracing. Gets a recorded rasterization of this layer's contents that
// can be displayed inside representations of this layer. May return null, in // can be displayed inside representations of this layer. May return null, in

@ -230,7 +230,7 @@ void PictureLayer::RunMicroBenchmark(MicroBenchmark* benchmark) {
} }
void PictureLayer::CaptureContent(const gfx::Rect& rect, void PictureLayer::CaptureContent(const gfx::Rect& rect,
std::vector<NodeId>* content) { std::vector<NodeInfo>* content) {
if (!DrawsContent()) if (!DrawsContent())
return; return;
@ -253,6 +253,22 @@ void PictureLayer::CaptureContent(const gfx::Rect& rect,
return; return;
display_item_list->CaptureContent(transformed, content); display_item_list->CaptureContent(transformed, content);
if (auto* outer_viewport_layer = layer_tree_host()->LayerByElementId(
layer_tree_host()->OuterViewportScrollElementId())) {
if (transform_tree_index() == outer_viewport_layer->transform_tree_index())
return;
gfx::Transform inverse_outer_screen_space_transform;
if (!outer_viewport_layer->ScreenSpaceTransform().GetInverse(
&inverse_outer_screen_space_transform)) {
return;
}
gfx::Transform combined_transform{ScreenSpaceTransform(),
inverse_outer_screen_space_transform};
for (auto& i : *content) {
i.visual_rect = MathUtil::ProjectEnclosingClippedRect(combined_transform,
i.visual_rect);
}
}
} }
void PictureLayer::DropRecordingSourceContentIfInvalid() { void PictureLayer::DropRecordingSourceContentIfInvalid() {

@ -47,7 +47,7 @@ class CC_EXPORT PictureLayer : public Layer {
bool Update() override; bool Update() override;
void RunMicroBenchmark(MicroBenchmark* benchmark) override; void RunMicroBenchmark(MicroBenchmark* benchmark) override;
void CaptureContent(const gfx::Rect& rect, void CaptureContent(const gfx::Rect& rect,
std::vector<NodeId>* content) override; std::vector<NodeInfo>* content) override;
ContentLayerClient* client() { return picture_layer_inputs_.client; } ContentLayerClient* client() { return picture_layer_inputs_.client; }

@ -6,6 +6,7 @@
#include <stddef.h> #include <stddef.h>
#include <map>
#include <string> #include <string>
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
@ -17,6 +18,7 @@
#include "third_party/skia/include/core/SkPictureRecorder.h" #include "third_party/skia/include/core/SkPictureRecorder.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/skia_util.h" #include "ui/gfx/skia_util.h"
namespace cc { namespace cc {
@ -32,28 +34,37 @@ bool GetCanvasClipBounds(SkCanvas* canvas, gfx::Rect* clip_bounds) {
} }
template <typename Function> template <typename Function>
void IterateTextContent(const PaintOpBuffer* buffer, const Function& yield) { void IterateTextContent(const PaintOpBuffer& buffer,
for (auto* op : PaintOpBuffer::Iterator(buffer)) { const Function& yield,
const gfx::Rect& rect) {
if (!buffer.has_draw_text_ops())
return;
for (auto* op : PaintOpBuffer::Iterator(&buffer)) {
if (op->GetType() == PaintOpType::DrawTextBlob) { if (op->GetType() == PaintOpType::DrawTextBlob) {
yield(static_cast<DrawTextBlobOp*>(op)); yield(static_cast<DrawTextBlobOp*>(op), rect);
} else if (op->GetType() == PaintOpType::DrawRecord) { } else if (op->GetType() == PaintOpType::DrawRecord) {
IterateTextContent(static_cast<DrawRecordOp*>(op)->record.get(), yield); IterateTextContent(*static_cast<DrawRecordOp*>(op)->record.get(), yield,
rect);
} }
} }
} }
template <typename Function> template <typename Function>
void IterateTextContentByOffsets(const PaintOpBuffer* buffer, void IterateTextContentByOffsets(const PaintOpBuffer& buffer,
const std::vector<size_t>& offsets, const std::vector<size_t>& offsets,
const std::vector<gfx::Rect>& rects,
const Function& yield) { const Function& yield) {
if (!buffer) DCHECK(buffer.has_draw_text_ops());
return; DCHECK_EQ(rects.size(), offsets.size());
for (auto* op : PaintOpBuffer::OffsetIterator(buffer, &offsets)) { size_t index = 0;
for (auto* op : PaintOpBuffer::OffsetIterator(&buffer, &offsets)) {
if (op->GetType() == PaintOpType::DrawTextBlob) { if (op->GetType() == PaintOpType::DrawTextBlob) {
yield(static_cast<DrawTextBlobOp*>(op)); yield(static_cast<DrawTextBlobOp*>(op), rects[index]);
} else if (op->GetType() == PaintOpType::DrawRecord) { } else if (op->GetType() == PaintOpType::DrawRecord) {
IterateTextContent(static_cast<DrawRecordOp*>(op)->record.get(), yield); IterateTextContent(*static_cast<DrawRecordOp*>(op)->record.get(), yield,
rects[index]);
} }
++index;
} }
} }
@ -90,20 +101,35 @@ void DisplayItemList::Raster(SkCanvas* canvas,
} }
void DisplayItemList::CaptureContent(const gfx::Rect& rect, void DisplayItemList::CaptureContent(const gfx::Rect& rect,
std::vector<NodeId>* content) const { std::vector<NodeInfo>* content) const {
if (!paint_op_buffer_.has_draw_text_ops())
return;
std::vector<size_t> offsets; std::vector<size_t> offsets;
rtree_.Search(rect, &offsets); std::vector<gfx::Rect> rects;
rtree_.Search(rect, &offsets, &rects);
IterateTextContentByOffsets( IterateTextContentByOffsets(
&paint_op_buffer_, offsets, paint_op_buffer_, offsets, rects,
[content](const DrawTextBlobOp* op) { content->push_back(op->node_id); }); [content](const DrawTextBlobOp* op, const gfx::Rect& rect) {
// Only union the rect if the current is the same as the last one.
if (!content->empty() && content->back().node_id == op->node_id)
content->back().visual_rect.Union(rect);
else
content->emplace_back(op->node_id, rect);
});
} }
double DisplayItemList::AreaOfDrawText(const gfx::Rect& rect) const { double DisplayItemList::AreaOfDrawText(const gfx::Rect& rect) const {
std::vector<size_t> offsets;
rtree_.Search(rect, &offsets);
double area = 0; double area = 0;
if (!paint_op_buffer_.has_draw_text_ops())
return area;
std::vector<size_t> offsets;
std::vector<gfx::Rect> rects;
rtree_.Search(rect, &offsets, &rects);
IterateTextContentByOffsets( IterateTextContentByOffsets(
&paint_op_buffer_, offsets, [&area](const DrawTextBlobOp* op) { paint_op_buffer_, offsets, rects,
[&area](const DrawTextBlobOp* op, const gfx::Rect& rect) {
// TODO(wangxianzhu) : crbug.com/1126582 use the visual_rect from
// callback.
// This is not fully accurate, e.g. when there is transform operations, // This is not fully accurate, e.g. when there is transform operations,
// but is good for statistics purpose. // but is good for statistics purpose.
SkRect bounds = op->blob->bounds(); SkRect bounds = op->blob->bounds();

@ -65,7 +65,7 @@ class CC_PAINT_EXPORT DisplayItemList
// Captures |DrawTextBlobOp|s intersecting |rect| and returns the associated // Captures |DrawTextBlobOp|s intersecting |rect| and returns the associated
// |NodeId|s in |content|. // |NodeId|s in |content|.
void CaptureContent(const gfx::Rect& rect, void CaptureContent(const gfx::Rect& rect,
std::vector<NodeId>* content) const; std::vector<NodeInfo>* content) const;
// Returns the approximate total area covered by |DrawTextBlobOp|s // Returns the approximate total area covered by |DrawTextBlobOp|s
// intersecting |rect|, used for statistics purpose. // intersecting |rect|, used for statistics purpose.

@ -5,6 +5,9 @@
#ifndef CC_PAINT_NODE_ID_H_ #ifndef CC_PAINT_NODE_ID_H_
#define CC_PAINT_NODE_ID_H_ #define CC_PAINT_NODE_ID_H_
#include "cc/paint/paint_export.h"
#include "ui/gfx/geometry/rect.h"
namespace cc { namespace cc {
// The NodeId is used to associate the DOM node with PaintOp, its peer in // The NodeId is used to associate the DOM node with PaintOp, its peer in
// blink is DOMNodeId. // blink is DOMNodeId.
@ -17,6 +20,14 @@ using NodeId = int;
static const NodeId kInvalidNodeId = 0; static const NodeId kInvalidNodeId = 0;
struct CC_PAINT_EXPORT NodeInfo {
NodeInfo(NodeId node_id, const gfx::Rect& visual_rect)
: node_id(node_id), visual_rect(visual_rect) {}
NodeId node_id;
gfx::Rect visual_rect;
};
} // namespace cc } // namespace cc
#endif // CC_PAINT_NODE_ID_H_ #endif // CC_PAINT_NODE_ID_H_

@ -774,15 +774,17 @@ std::string LayerTreeHost::LayersAsString() const {
return layers; return layers;
} }
bool LayerTreeHost::CaptureContent(std::vector<NodeId>* content) { bool LayerTreeHost::CaptureContent(std::vector<NodeInfo>* content) {
if (viewport_visible_rect_.IsEmpty()) if (viewport_visible_rect_.IsEmpty())
return false; return false;
gfx::Rect rect = gfx::Rect(viewport_visible_rect_.width(), gfx::Rect rect = gfx::Rect(viewport_visible_rect_.width(),
viewport_visible_rect_.height()); viewport_visible_rect_.height());
for (auto* layer : *this) for (auto* layer : *this) {
// Normally, the node won't be drawn in multiple layers, even it is, such as
// text strokes, the visual rect don't have too much different.
layer->CaptureContent(rect, content); layer->CaptureContent(rect, content);
}
return true; return true;
} }

@ -706,8 +706,8 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient {
std::string LayersAsString() const; std::string LayersAsString() const;
// Captures the on-screen text content, if success, fills the associated // Captures the on-screen text content, if success, fills the associated
// NodeId in |content| and return true, otherwise return false. // NodeInfo in |content| and return true, otherwise return false.
bool CaptureContent(std::vector<NodeId>* content); bool CaptureContent(std::vector<NodeInfo>* content);
std::unique_ptr<BeginMainFrameMetrics> begin_main_frame_metrics() { std::unique_ptr<BeginMainFrameMetrics> begin_main_frame_metrics() {
return std::move(begin_main_frame_metrics_); return std::move(begin_main_frame_metrics_);

@ -81,7 +81,7 @@ class LayerTreeHostCaptureContentTest : public LayerTreeTest {
for (auto& c : captured_content_) { for (auto& c : captured_content_) {
for (auto it = expected_result->begin(); it != expected_result->end(); for (auto it = expected_result->begin(); it != expected_result->end();
++it) { ++it) {
if (it->node_id() == c) { if (it->node_id() == c.node_id) {
expected_result->erase(it); expected_result->erase(it);
break; break;
} }
@ -107,7 +107,7 @@ class LayerTreeHostCaptureContentTest : public LayerTreeTest {
} }
scoped_refptr<FakePictureLayer> root_picture_layer_; scoped_refptr<FakePictureLayer> root_picture_layer_;
std::vector<NodeId> captured_content_; std::vector<NodeInfo> captured_content_;
const gfx::Size device_bounds_; const gfx::Size device_bounds_;
base::WeakPtrFactory<LayerTreeHostCaptureContentTest> weak_factory_{this}; base::WeakPtrFactory<LayerTreeHostCaptureContentTest> weak_factory_{this};
}; };

@ -16,7 +16,7 @@ const base::Feature kContentCapture{"ContentCapture",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kContentCaptureTriggeringForExperiment{ const base::Feature kContentCaptureTriggeringForExperiment{
"ContentCaptureTriggeringForExperiment", base::FEATURE_DISABLED_BY_DEFAULT}; "ContentCaptureTriggeringForExperiment", base::FEATURE_ENABLED_BY_DEFAULT};
#else #else
const base::Feature kContentCapture{"ContentCapture", const base::Feature kContentCapture{"ContentCapture",
base::FEATURE_DISABLED_BY_DEFAULT}; base::FEATURE_DISABLED_BY_DEFAULT};

@ -13,7 +13,7 @@
namespace blink { namespace blink {
class Node; class ContentHolder;
// The class to represent the captured content. // The class to represent the captured content.
class BLINK_EXPORT WebContentHolder { class BLINK_EXPORT WebContentHolder {
@ -27,11 +27,11 @@ class BLINK_EXPORT WebContentHolder {
uint64_t GetId() const; uint64_t GetId() const;
#if INSIDE_BLINK #if INSIDE_BLINK
WebContentHolder(Node& node); explicit WebContentHolder(ContentHolder& node_info);
#endif #endif
private: private:
WebPrivatePtr<Node> private_; WebPrivatePtr<ContentHolder> private_;
}; };
} // namespace blink } // namespace blink

@ -12,6 +12,8 @@ blink_core_sources("content_capture") {
"content_capture_task.h", "content_capture_task.h",
"content_capture_task_histogram_reporter.cc", "content_capture_task_histogram_reporter.cc",
"content_capture_task_histogram_reporter.h", "content_capture_task_histogram_reporter.h",
"content_holder.cc",
"content_holder.h",
"sent_nodes.cc", "sent_nodes.cc",
"sent_nodes.h", "sent_nodes.h",
"task_session.cc", "task_session.cc",

@ -74,7 +74,7 @@ void ContentCaptureTask::Shutdown() {
local_frame_root_ = nullptr; local_frame_root_ = nullptr;
} }
bool ContentCaptureTask::CaptureContent(Vector<cc::NodeId>& data) { bool ContentCaptureTask::CaptureContent(Vector<cc::NodeInfo>& data) {
if (captured_content_for_testing_) { if (captured_content_for_testing_) {
data = captured_content_for_testing_.value(); data = captured_content_for_testing_.value();
return true; return true;
@ -84,7 +84,7 @@ bool ContentCaptureTask::CaptureContent(Vector<cc::NodeId>& data) {
if (const auto* root_frame_view = local_frame_root_->View()) { if (const auto* root_frame_view = local_frame_root_->View()) {
if (const auto* cc_layer = root_frame_view->RootCcLayer()) { if (const auto* cc_layer = root_frame_view->RootCcLayer()) {
if (auto* layer_tree_host = cc_layer->layer_tree_host()) { if (auto* layer_tree_host = cc_layer->layer_tree_host()) {
std::vector<cc::NodeId> content; std::vector<cc::NodeInfo> content;
if (layer_tree_host->CaptureContent(&content)) { if (layer_tree_host->CaptureContent(&content)) {
for (auto c : content) for (auto c : content)
data.push_back(std::move(c)); data.push_back(std::move(c));
@ -99,7 +99,7 @@ bool ContentCaptureTask::CaptureContent(Vector<cc::NodeId>& data) {
bool ContentCaptureTask::CaptureContent() { bool ContentCaptureTask::CaptureContent() {
DCHECK(task_session_); DCHECK(task_session_);
Vector<cc::NodeId> buffer; Vector<cc::NodeInfo> buffer;
if (histogram_reporter_) if (histogram_reporter_)
histogram_reporter_->OnCaptureContentStarted(); histogram_reporter_->OnCaptureContentStarted();
bool result = CaptureContent(buffer); bool result = CaptureContent(buffer);
@ -124,14 +124,14 @@ void ContentCaptureTask::SendContent(
// Only send changed content after the new content was sent. // Only send changed content after the new content was sent.
bool sending_changed_content = !doc_session.HasUnsentCapturedContent(); bool sending_changed_content = !doc_session.HasUnsentCapturedContent();
while (content_batch.size() < kBatchSize) { while (content_batch.size() < kBatchSize) {
Node* node; ContentHolder* holder;
if (sending_changed_content) if (sending_changed_content)
node = doc_session.GetNextChangedNode(); holder = doc_session.GetNextChangedNode();
else else
node = doc_session.GetNextUnsentNode(); holder = doc_session.GetNextUnsentNode();
if (!node) if (!holder)
break; break;
content_batch.emplace_back(WebContentHolder(*node)); content_batch.emplace_back(WebContentHolder(*holder));
} }
if (!content_batch.empty()) { if (!content_batch.empty()) {
if (sending_changed_content) { if (sending_changed_content) {

@ -88,7 +88,7 @@ class CORE_EXPORT ContentCaptureTask
} }
void SetCapturedContentForTesting( void SetCapturedContentForTesting(
const Vector<cc::NodeId>& captured_content) { const Vector<cc::NodeInfo>& captured_content) {
captured_content_for_testing_ = captured_content; captured_content_for_testing_ = captured_content;
} }
@ -133,7 +133,7 @@ class CORE_EXPORT ContentCaptureTask
base::TimeDelta GetAndAdjustDelay(ScheduleReason reason); base::TimeDelta GetAndAdjustDelay(ScheduleReason reason);
void ScheduleInternal(ScheduleReason reason); void ScheduleInternal(ScheduleReason reason);
bool CaptureContent(Vector<cc::NodeId>& data); bool CaptureContent(Vector<cc::NodeInfo>& data);
// Indicates if there is content change since last run. // Indicates if there is content change since last run.
bool has_content_change_ = false; bool has_content_change_ = false;
@ -147,7 +147,7 @@ class CORE_EXPORT ContentCaptureTask
scoped_refptr<ContentCaptureTaskHistogramReporter> histogram_reporter_; scoped_refptr<ContentCaptureTaskHistogramReporter> histogram_reporter_;
base::Optional<TaskState> task_stop_for_testing_; base::Optional<TaskState> task_stop_for_testing_;
base::Optional<Vector<cc::NodeId>> captured_content_for_testing_; base::Optional<Vector<cc::NodeInfo>> captured_content_for_testing_;
}; };
} // namespace blink } // namespace blink

@ -26,6 +26,12 @@
namespace blink { namespace blink {
namespace {
gfx::Rect GetRect(LayoutObject* layout_object) {
return gfx::Rect(EnclosingIntRect(layout_object->VisualRectInDocument()));
}
} // namespace
class WebContentCaptureClientTestHelper : public WebContentCaptureClient { class WebContentCaptureClientTestHelper : public WebContentCaptureClient {
public: public:
~WebContentCaptureClientTestHelper() override = default; ~WebContentCaptureClientTestHelper() override = default;
@ -212,7 +218,8 @@ class ContentCaptureTest : public PageTestBase,
UpdateAllLifecyclePhasesForTest(); UpdateAllLifecyclePhasesForTest();
GetContentCaptureManager()->ScheduleTaskIfNeeded(*node); GetContentCaptureManager()->ScheduleTaskIfNeeded(*node);
created_node_id_ = DOMNodeIds::IdForNode(node); created_node_id_ = DOMNodeIds::IdForNode(node);
Vector<DOMNodeId> captured_content{created_node_id_}; Vector<cc::NodeInfo> captured_content{
cc::NodeInfo(created_node_id_, GetRect(node->GetLayoutObject()))};
content_capture_manager_->GetContentCaptureTask() content_capture_manager_->GetContentCaptureTask()
->SetCapturedContentForTesting(captured_content); ->SetCapturedContentForTesting(captured_content);
} }
@ -269,7 +276,7 @@ class ContentCaptureTest : public PageTestBase,
return node_ids_.size() - GetExpectedFirstResultSize(); return node_ids_.size() - GetExpectedFirstResultSize();
} }
const Vector<DOMNodeId>& NodeIds() const { return node_ids_; } const Vector<cc::NodeInfo>& NodeIds() const { return node_ids_; }
const Vector<Persistent<Node>> Nodes() const { return nodes_; } const Vector<Persistent<Node>> Nodes() const { return nodes_; }
private: private:
@ -288,12 +295,13 @@ class ContentCaptureTest : public PageTestBase,
CHECK(layout_object->IsText()); CHECK(layout_object->IsText());
nodes_.push_back(node); nodes_.push_back(node);
GetContentCaptureManager()->ScheduleTaskIfNeeded(*node); GetContentCaptureManager()->ScheduleTaskIfNeeded(*node);
node_ids_.push_back(DOMNodeIds::IdForNode(node)); node_ids_.push_back(
cc::NodeInfo(DOMNodeIds::IdForNode(node), GetRect(layout_object)));
} }
} }
Vector<Persistent<Node>> nodes_; Vector<Persistent<Node>> nodes_;
Vector<DOMNodeId> node_ids_; Vector<cc::NodeInfo> node_ids_;
std::unique_ptr<WebContentCaptureClientTestHelper> content_capture_client_; std::unique_ptr<WebContentCaptureClientTestHelper> content_capture_client_;
Persistent<ContentCaptureManagerTestHelper> content_capture_manager_; Persistent<ContentCaptureManagerTestHelper> content_capture_manager_;
Persistent<ContentCaptureLocalFrameClientHelper> local_frame_client_; Persistent<ContentCaptureLocalFrameClientHelper> local_frame_client_;
@ -636,7 +644,7 @@ class ContentCaptureSimTest : public SimTest {
} else if (type == ContentType::kChildFrame) { } else if (type == ContentType::kChildFrame) {
SetCapturedContent(child_frame_content_); SetCapturedContent(child_frame_content_);
} else if (type == ContentType::kAll) { } else if (type == ContentType::kAll) {
Vector<DOMNodeId> holders(main_frame_content_); Vector<cc::NodeInfo> holders(main_frame_content_);
holders.AppendRange(child_frame_content_.begin(), holders.AppendRange(child_frame_content_.begin(),
child_frame_content_.end()); child_frame_content_.end());
SetCapturedContent(holders); SetCapturedContent(holders);
@ -765,18 +773,20 @@ class ContentCaptureSimTest : public SimTest {
EXPECT_EQ(2u, child_frame_content_.size()); EXPECT_EQ(2u, child_frame_content_.size());
} }
void InitNodeHolders(Vector<DOMNodeId>& buffer, void InitNodeHolders(Vector<cc::NodeInfo>& buffer,
const Vector<std::string>& ids, const Vector<std::string>& ids,
const Document& document) { const Document& document) {
for (auto id : ids) { for (auto id : ids) {
LayoutText* layout_text = ToLayoutText( auto* layout_object =
document.getElementById(id.c_str())->firstChild()->GetLayoutObject()); document.getElementById(id.c_str())->firstChild()->GetLayoutObject();
LayoutText* layout_text = ToLayoutText(layout_object);
EXPECT_TRUE(layout_text->HasNodeId()); EXPECT_TRUE(layout_text->HasNodeId());
buffer.push_back(layout_text->EnsureNodeId()); buffer.push_back(
cc::NodeInfo(layout_text->EnsureNodeId(), GetRect(layout_object)));
} }
} }
void AddNodeToDocument(Document& doc, Vector<DOMNodeId>& buffer) { void AddNodeToDocument(Document& doc, Vector<cc::NodeInfo>& buffer) {
Node* node = doc.createTextNode("New Text"); Node* node = doc.createTextNode("New Text");
auto* element = MakeGarbageCollected<Element>(html_names::kPTag, &doc); auto* element = MakeGarbageCollected<Element>(html_names::kPTag, &doc);
element->appendChild(node); element->appendChild(node);
@ -785,7 +795,8 @@ class ContentCaptureSimTest : public SimTest {
Compositor().BeginFrame(); Compositor().BeginFrame();
LayoutText* layout_text = ToLayoutText(node->GetLayoutObject()); LayoutText* layout_text = ToLayoutText(node->GetLayoutObject());
EXPECT_TRUE(layout_text->HasNodeId()); EXPECT_TRUE(layout_text->HasNodeId());
buffer.push_front(layout_text->EnsureNodeId()); buffer.push_back(cc::NodeInfo(layout_text->EnsureNodeId(),
GetRect(node->GetLayoutObject())));
} }
void InsertNodeContent(Document& doc, void InsertNodeContent(Document& doc,
@ -807,7 +818,7 @@ class ContentCaptureSimTest : public SimTest {
Compositor().BeginFrame(); Compositor().BeginFrame();
} }
void SetCapturedContent(const Vector<DOMNodeId>& captured_content) { void SetCapturedContent(const Vector<cc::NodeInfo>& captured_content) {
GetDocument() GetDocument()
.GetFrame() .GetFrame()
->LocalFrameRoot() ->LocalFrameRoot()
@ -818,8 +829,8 @@ class ContentCaptureSimTest : public SimTest {
Vector<std::string> main_frame_expected_text_; Vector<std::string> main_frame_expected_text_;
Vector<std::string> child_frame_expected_text_; Vector<std::string> child_frame_expected_text_;
Vector<DOMNodeId> main_frame_content_; Vector<cc::NodeInfo> main_frame_content_;
Vector<DOMNodeId> child_frame_content_; Vector<cc::NodeInfo> child_frame_content_;
WebContentCaptureClientTestHelper client_; WebContentCaptureClientTestHelper client_;
WebContentCaptureClientTestHelper child_client_; WebContentCaptureClientTestHelper child_client_;
Persistent<Document> child_document_; Persistent<Document> child_document_;

@ -0,0 +1,20 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/content_capture/content_holder.h"
namespace blink {
ContentHolder::ContentHolder() = default;
ContentHolder::ContentHolder(Node* node, const gfx::Rect& rect)
: node_(node), rect_(rect) {}
ContentHolder::~ContentHolder() = default;
void ContentHolder::Trace(Visitor* visitor) const {
visitor->Trace(node_);
}
} // namespace blink

@ -0,0 +1,36 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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_CORE_CONTENT_CAPTURE_CONTENT_HOLDER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CONTENT_CAPTURE_CONTENT_HOLDER_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/member.h"
#include "ui/gfx/geometry/rect.h"
namespace blink {
class Node;
class CORE_EXPORT ContentHolder : public GarbageCollected<ContentHolder> {
public:
ContentHolder();
ContentHolder(Node* node, const gfx::Rect& rect);
virtual ~ContentHolder();
Node* node() const { return node_; }
const gfx::Rect& rect() const { return rect_; }
void Trace(Visitor*) const;
private:
WeakMember<Node> node_;
gfx::Rect rect_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CONTENT_CAPTURE_CONTENT_HOLDER_H_

@ -22,40 +22,46 @@ TaskSession::DocumentSession::~DocumentSession() {
callback_.value().Run(total_sent_nodes_); callback_.value().Run(total_sent_nodes_);
} }
void TaskSession::DocumentSession::AddCapturedNode(Node& node) { void TaskSession::DocumentSession::AddCapturedNode(Node& node,
captured_content_.insert(WeakMember<Node>(&node)); const gfx::Rect& rect) {
// Replace the previous rect if any.
captured_content_.Set(WeakMember<Node>(&node), rect);
} }
void TaskSession::DocumentSession::AddDetachedNode(int64_t id) { void TaskSession::DocumentSession::AddDetachedNode(int64_t id) {
detached_nodes_.emplace_back(id); detached_nodes_.emplace_back(id);
} }
void TaskSession::DocumentSession::AddChangedNode(Node& node) { void TaskSession::DocumentSession::AddChangedNode(Node& node,
changed_content_.insert(WeakMember<Node>(&node)); const gfx::Rect& rect) {
// Replace the previous rect if any.
changed_content_.Set(WeakMember<Node>(&node), rect);
} }
WebVector<int64_t> TaskSession::DocumentSession::MoveDetachedNodes() { WebVector<int64_t> TaskSession::DocumentSession::MoveDetachedNodes() {
return std::move(detached_nodes_); return std::move(detached_nodes_);
} }
Node* TaskSession::DocumentSession::GetNextUnsentNode() { ContentHolder* TaskSession::DocumentSession::GetNextUnsentNode() {
while (!captured_content_.IsEmpty()) { while (!captured_content_.IsEmpty()) {
Node* node = captured_content_.TakeAny().Get(); auto node = captured_content_.begin()->key;
const gfx::Rect rect = captured_content_.Take(node);
if (node && node->GetLayoutObject() && !sent_nodes_->HasSent(*node)) { if (node && node->GetLayoutObject() && !sent_nodes_->HasSent(*node)) {
sent_nodes_->OnSent(*node); sent_nodes_->OnSent(*node);
total_sent_nodes_++; total_sent_nodes_++;
return node; return MakeGarbageCollected<ContentHolder>(node, rect);
} }
} }
return nullptr; return nullptr;
} }
Node* TaskSession::DocumentSession::GetNextChangedNode() { ContentHolder* TaskSession::DocumentSession::GetNextChangedNode() {
while (!changed_content_.IsEmpty()) { while (!changed_content_.IsEmpty()) {
Node* node = changed_content_.TakeAny().Get(); auto node = changed_content_.begin()->key;
if (node && node->GetLayoutObject()) { const gfx::Rect rect = changed_content_.Take(node);
if (node.Get() && node->GetLayoutObject()) {
total_sent_nodes_++; total_sent_nodes_++;
return node; return MakeGarbageCollected<ContentHolder>(node, rect);
} }
} }
return nullptr; return nullptr;
@ -87,7 +93,7 @@ TaskSession::DocumentSession* TaskSession::GetNextUnsentDocumentSession() {
} }
void TaskSession::SetCapturedContent( void TaskSession::SetCapturedContent(
const Vector<cc::NodeId>& captured_content) { const Vector<cc::NodeInfo>& captured_content) {
DCHECK(!HasUnsentData()); DCHECK(!HasUnsentData());
DCHECK(!captured_content.IsEmpty()); DCHECK(!captured_content.IsEmpty());
GroupCapturedContentByDocument(captured_content); GroupCapturedContentByDocument(captured_content);
@ -95,20 +101,26 @@ void TaskSession::SetCapturedContent(
} }
void TaskSession::GroupCapturedContentByDocument( void TaskSession::GroupCapturedContentByDocument(
const Vector<cc::NodeId>& captured_content) { const Vector<cc::NodeInfo>& captured_content) {
for (const cc::NodeId& node_id : captured_content) { // In rare cases, the same node could have multiple entries in the
if (Node* node = DOMNodeIds::NodeForId(node_id)) { // |captured_content|, but the visual_rect are almost same, we just let the
// later replace the previous.
for (const auto& i : captured_content) {
if (Node* node = DOMNodeIds::NodeForId(i.node_id)) {
if (changed_nodes_.Take(node)) { if (changed_nodes_.Take(node)) {
// The changed node might not be sent. // The changed node might not be sent.
if (sent_nodes_->HasSent(*node)) { if (sent_nodes_->HasSent(*node)) {
EnsureDocumentSession(node->GetDocument()).AddChangedNode(*node); EnsureDocumentSession(node->GetDocument())
.AddChangedNode(*node, i.visual_rect);
} else { } else {
EnsureDocumentSession(node->GetDocument()).AddCapturedNode(*node); EnsureDocumentSession(node->GetDocument())
.AddCapturedNode(*node, i.visual_rect);
} }
continue; continue;
} }
if (!sent_nodes_->HasSent(*node)) { if (!sent_nodes_->HasSent(*node)) {
EnsureDocumentSession(node->GetDocument()).AddCapturedNode(*node); EnsureDocumentSession(node->GetDocument())
.AddCapturedNode(*node, i.visual_rect);
} }
} }
} }

@ -11,10 +11,12 @@
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "cc/paint/node_id.h" #include "cc/paint/node_id.h"
#include "third_party/blink/public/platform/web_vector.h" #include "third_party/blink/public/platform/web_vector.h"
#include "third_party/blink/renderer/core/content_capture/content_holder.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h" #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/heap/member.h" #include "third_party/blink/renderer/platform/heap/member.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/vector.h" #include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink { namespace blink {
@ -52,9 +54,9 @@ class TaskSession final : public GarbageCollected<TaskSession> {
SentNodes& sent_nodes, SentNodes& sent_nodes,
SentNodeCountCallback& call_back); SentNodeCountCallback& call_back);
~DocumentSession(); ~DocumentSession();
void AddCapturedNode(Node& node); void AddCapturedNode(Node& node, const gfx::Rect& rect);
void AddDetachedNode(int64_t id); void AddDetachedNode(int64_t id);
void AddChangedNode(Node& node); void AddChangedNode(Node& node, const gfx::Rect& rect);
bool HasUnsentData() const { bool HasUnsentData() const {
return HasUnsentCapturedContent() || HasUnsentChangedContent() || return HasUnsentCapturedContent() || HasUnsentChangedContent() ||
HasUnsentDetachedNodes(); HasUnsentDetachedNodes();
@ -70,9 +72,9 @@ class TaskSession final : public GarbageCollected<TaskSession> {
void SetFirstDataHasSent() { first_data_has_sent_ = true; } void SetFirstDataHasSent() { first_data_has_sent_ = true; }
// Removes the unsent node from |captured_content_|, and returns it. // Removes the unsent node from |captured_content_|, and returns it.
Node* GetNextUnsentNode(); ContentHolder* GetNextUnsentNode();
Node* GetNextChangedNode(); ContentHolder* GetNextChangedNode();
// Resets the |captured_content_| and the |detached_nodes_|, shall only be // Resets the |captured_content_| and the |detached_nodes_|, shall only be
// used if those data doesn't need to be sent, e.g. there is no // used if those data doesn't need to be sent, e.g. there is no
@ -83,14 +85,14 @@ class TaskSession final : public GarbageCollected<TaskSession> {
private: private:
// The captured content that belongs to this document. // The captured content that belongs to this document.
HeapHashSet<WeakMember<Node>> captured_content_; HeapHashMap<WeakMember<Node>, gfx::Rect> captured_content_;
// The list of content id of node that has been detached from the // The list of content id of node that has been detached from the
// LayoutTree. // LayoutTree.
WebVector<int64_t> detached_nodes_; WebVector<int64_t> detached_nodes_;
WeakMember<const Document> document_; WeakMember<const Document> document_;
Member<SentNodes> sent_nodes_; Member<SentNodes> sent_nodes_;
// The list of changed nodes that needs to be sent. // The list of changed nodes that needs to be sent.
HeapHashSet<WeakMember<Node>> changed_content_; HeapHashMap<WeakMember<Node>, gfx::Rect> changed_content_;
bool first_data_has_sent_ = false; bool first_data_has_sent_ = false;
// This is for the metrics to record the total node that has been sent. // This is for the metrics to record the total node that has been sent.
@ -107,7 +109,7 @@ class TaskSession final : public GarbageCollected<TaskSession> {
// This can only be invoked when all data has been sent (i.e. HasUnsentData() // This can only be invoked when all data has been sent (i.e. HasUnsentData()
// returns False). // returns False).
void SetCapturedContent(const Vector<cc::NodeId>& captured_content); void SetCapturedContent(const Vector<cc::NodeInfo>& captured_content);
void OnNodeDetached(const Node& node); void OnNodeDetached(const Node& node);
@ -126,7 +128,7 @@ class TaskSession final : public GarbageCollected<TaskSession> {
private: private:
void GroupCapturedContentByDocument( void GroupCapturedContentByDocument(
const Vector<cc::NodeId>& captured_content); const Vector<cc::NodeInfo>& captured_content);
DocumentSession& EnsureDocumentSession(const Document& doc); DocumentSession& EnsureDocumentSession(const Document& doc);
DocumentSession* GetDocumentSession(const Document& document) const; DocumentSession* GetDocumentSession(const Document& document) const;

@ -4,6 +4,7 @@
#include "third_party/blink/public/web/web_content_holder.h" #include "third_party/blink/public/web/web_content_holder.h"
#include "third_party/blink/renderer/core/content_capture/content_holder.h"
#include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_object.h"
@ -23,19 +24,17 @@ WebContentHolder::~WebContentHolder() {
} }
WebString WebContentHolder::GetValue() const { WebString WebContentHolder::GetValue() const {
return private_->nodeValue(); return private_->node()->nodeValue();
} }
WebRect WebContentHolder::GetBoundingBox() const { WebRect WebContentHolder::GetBoundingBox() const {
if (auto* layout_obj = private_->GetLayoutObject()) return WebRect(private_->rect());
return EnclosingIntRect(layout_obj->VisualRectInDocument());
return IntRect();
} }
uint64_t WebContentHolder::GetId() const { uint64_t WebContentHolder::GetId() const {
return reinterpret_cast<uint64_t>(private_.Get()); return reinterpret_cast<uint64_t>(private_->node());
} }
WebContentHolder::WebContentHolder(Node& node) : private_(&node) {} WebContentHolder::WebContentHolder(ContentHolder& holder) : private_(&holder) {}
} // namespace blink } // namespace blink

@ -257,6 +257,7 @@ _CONFIG = [
'cc::PaintShader', 'cc::PaintShader',
'cc::PaintWorkletInput', 'cc::PaintWorkletInput',
'cc::NodeId', 'cc::NodeId',
'cc::NodeInfo',
# Chromium geometry types. # Chromium geometry types.
'gfx::Point', 'gfx::Point',