blob: cd95473a09675a6463144a1226ea90fff39657f0 [file] [log] [blame]
// Copyright 2019 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/task_session.h"
#include <utility>
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
namespace blink {
namespace {
bool IsConstantStreamingEnabled() {
return base::FeatureList::IsEnabled(
features::kContentCaptureConstantStreaming);
}
} // namespace
TaskSession::DocumentSession::DocumentSession(const Document& document,
SentNodeCountCallback& callback)
: document_(&document), callback_(callback) {}
TaskSession::DocumentSession::~DocumentSession() {
if (callback_.has_value())
callback_.value().Run(total_sent_nodes_);
}
bool TaskSession::DocumentSession::AddDetachedNode(const Node& node) {
// Take the node out of |sent_nodes|, otherwise, the |node| would be found
// invisible in next capturing and be reported as the removed node again.
if (sent_nodes_.Take(&node)) {
detached_nodes_.emplace_back(reinterpret_cast<int64_t>(&node));
return true;
}
return false;
}
WebVector<int64_t> TaskSession::DocumentSession::MoveDetachedNodes() {
return std::move(detached_nodes_);
}
ContentHolder* TaskSession::DocumentSession::GetNextUnsentNode() {
while (!captured_content_.IsEmpty()) {
auto node = captured_content_.begin()->key;
const gfx::Rect rect = captured_content_.Take(node);
if (node && node->GetLayoutObject() && !sent_nodes_.Contains(node)) {
sent_nodes_.insert(WeakMember<const Node>(node));
total_sent_nodes_++;
return MakeGarbageCollected<ContentHolder>(node, rect);
}
}
return nullptr;
}
ContentHolder* TaskSession::DocumentSession::GetNextChangedNode() {
while (!changed_content_.IsEmpty()) {
auto node = changed_content_.begin()->key;
const gfx::Rect rect = changed_content_.Take(node);
if (node.Get() && node->GetLayoutObject()) {
total_sent_nodes_++;
return MakeGarbageCollected<ContentHolder>(node, rect);
}
}
return nullptr;
}
bool TaskSession::DocumentSession::AddChangedNode(Node& node) {
// No need to save the node that hasn't been sent because it will be captured
// once being on screen.
if (sent_nodes_.Contains(&node)) {
changed_nodes_.insert(WeakMember<Node>(&node));
return true;
}
return false;
}
void TaskSession::DocumentSession::OnContentCaptured(
Node& node,
const gfx::Rect& visual_rect) {
if (changed_nodes_.Take(&node)) {
changed_content_.Set(WeakMember<Node>(&node), visual_rect);
} else {
if (IsConstantStreamingEnabled()) {
if (auto value = sent_nodes_.Take(&node))
visible_sent_nodes_.insert(value);
else
captured_content_.Set(WeakMember<Node>(&node), visual_rect);
} else {
if (!sent_nodes_.Contains(&node))
captured_content_.Set(WeakMember<Node>(&node), visual_rect);
// else |node| has been sent and unchanged.
}
}
}
void TaskSession::DocumentSession::OnGroupingComplete() {
if (!IsConstantStreamingEnabled())
return;
// All nodes in |sent_nodes_| aren't visible any more, remove them.
for (auto weak_node : sent_nodes_) {
if (auto* node = weak_node.Get())
detached_nodes_.emplace_back(reinterpret_cast<int64_t>(node));
}
// |visible_sent_nodes_| are still visible and moved to |sent_nodes_|.
sent_nodes_.swap(visible_sent_nodes_);
visible_sent_nodes_.clear();
// Any node in |changed_nodes_| isn't visible any more and shall be clear.
changed_nodes_.clear();
}
void TaskSession::DocumentSession::Trace(Visitor* visitor) const {
visitor->Trace(captured_content_);
visitor->Trace(changed_content_);
visitor->Trace(document_);
visitor->Trace(sent_nodes_);
visitor->Trace(visible_sent_nodes_);
visitor->Trace(changed_nodes_);
}
void TaskSession::DocumentSession::Reset() {
changed_content_.clear();
captured_content_.clear();
detached_nodes_.Clear();
sent_nodes_.clear();
visible_sent_nodes_.clear();
changed_nodes_.clear();
}
TaskSession::TaskSession() = default;
TaskSession::DocumentSession* TaskSession::GetNextUnsentDocumentSession() {
for (auto& doc : to_document_session_.Values()) {
if (!doc->HasUnsentData())
continue;
return doc;
}
has_unsent_data_ = false;
return nullptr;
}
void TaskSession::SetCapturedContent(
const Vector<cc::NodeInfo>& captured_content) {
DCHECK(!HasUnsentData());
DCHECK(!captured_content.IsEmpty());
GroupCapturedContentByDocument(captured_content);
has_unsent_data_ = true;
}
void TaskSession::GroupCapturedContentByDocument(
const Vector<cc::NodeInfo>& captured_content) {
// In rare cases, the same node could have multiple entries in the
// |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)) {
EnsureDocumentSession(node->GetDocument())
.OnContentCaptured(*node, i.visual_rect);
}
}
for (auto doc_session : to_document_session_.Values()) {
doc_session->OnGroupingComplete();
}
}
void TaskSession::OnNodeDetached(const Node& node) {
if (EnsureDocumentSession(node.GetDocument()).AddDetachedNode(node))
has_unsent_data_ = true;
}
void TaskSession::OnNodeChanged(Node& node) {
if (EnsureDocumentSession(node.GetDocument()).AddChangedNode(node))
has_unsent_data_ = true;
}
TaskSession::DocumentSession& TaskSession::EnsureDocumentSession(
const Document& doc) {
DocumentSession* doc_session = GetDocumentSession(doc);
if (!doc_session) {
doc_session = MakeGarbageCollected<DocumentSession>(doc, callback_);
to_document_session_.insert(&doc, doc_session);
}
return *doc_session;
}
TaskSession::DocumentSession* TaskSession::GetDocumentSession(
const Document& document) const {
auto it = to_document_session_.find(&document);
if (it == to_document_session_.end())
return nullptr;
return it->value;
}
void TaskSession::Trace(Visitor* visitor) const {
visitor->Trace(to_document_session_);
}
void TaskSession::ClearDocumentSessionsForTesting() {
to_document_session_.clear();
}
} // namespace blink