blob: 64df06b5574444b901818d6d31dae9dd5490a932 [file] [log] [blame]
// Copyright 2021 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/inspector/inspector_performance_timeline_agent.h"
#include <utility>
#include "build/build_config.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
#include "third_party/blink/renderer/core/inspector/inspected_frames.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/largest_contentful_paint.h"
#include "third_party/blink/renderer/core/timing/layout_shift.h"
#include "third_party/blink/renderer/core/timing/layout_shift_attribution.h"
#include "third_party/blink/renderer/core/timing/worker_global_scope_performance.h"
namespace blink {
namespace {
constexpr PerformanceEntryType kSupportedTypes =
PerformanceEntry::EntryType::kLargestContentfulPaint |
PerformanceEntry::EntryType::kLayoutShift;
double ToProtocolTime(DOMHighResTimeStamp timeOrigin,
DOMHighResTimeStamp time) {
return time ? ConvertDOMHighResTimeStampToSeconds(timeOrigin + time) : 0.0;
}
std::unique_ptr<protocol::PerformanceTimeline::LargestContentfulPaint>
BuildEventDetails(const LargestContentfulPaint& lcp,
DOMHighResTimeStamp timeOrigin) {
auto result = protocol::PerformanceTimeline::LargestContentfulPaint::create()
.setRenderTime(ToProtocolTime(timeOrigin, lcp.renderTime()))
.setLoadTime(ToProtocolTime(timeOrigin, lcp.loadTime()))
.setSize(lcp.size())
.build();
if (!lcp.id().IsEmpty())
result->setElementId(lcp.id());
if (Element* element = lcp.element())
result->setNodeId(IdentifiersFactory::IntIdForNode(element));
if (!lcp.url().IsEmpty())
result->setUrl(lcp.url());
return result;
}
std::unique_ptr<protocol::DOM::Rect> BuildRect(DOMRectReadOnly* rect) {
return protocol::DOM::Rect::create()
.setX(rect->x())
.setY(rect->y())
.setWidth(rect->width())
.setHeight(rect->height())
.build();
}
std::unique_ptr<
protocol::Array<protocol::PerformanceTimeline::LayoutShiftAttribution>>
BuildLayoutShiftAttributions(const LayoutShift::AttributionList& attributions) {
auto result = std::make_unique<
protocol::Array<protocol::PerformanceTimeline::LayoutShiftAttribution>>();
for (const auto& attr : attributions) {
auto entry = protocol::PerformanceTimeline::LayoutShiftAttribution::create()
.setPreviousRect(BuildRect(attr->previousRect()))
.setCurrentRect(BuildRect(attr->currentRect()))
.build();
if (Node* node = attr->rawNodeForInspector())
entry->setNodeId(IdentifiersFactory::IntIdForNode(node));
result->push_back(std::move(entry));
}
return result;
}
std::unique_ptr<protocol::PerformanceTimeline::LayoutShift> BuildEventDetails(
const LayoutShift& ls,
DOMHighResTimeStamp timeOrigin) {
auto result =
protocol::PerformanceTimeline::LayoutShift::create()
.setValue(ls.value())
.setHadRecentInput(ls.hadRecentInput())
.setLastInputTime(ToProtocolTime(timeOrigin, ls.lastInputTime()))
.setSources(BuildLayoutShiftAttributions(ls.sources()))
.build();
return result;
}
std::unique_ptr<protocol::PerformanceTimeline::TimelineEvent>
BuildProtocolEvent(String frame_id,
DOMHighResTimeStamp timeOrigin,
PerformanceEntry* entry) {
auto result = protocol::PerformanceTimeline::TimelineEvent::create()
.setFrameId(frame_id)
.setType(entry->entryType())
.setName(entry->name())
// TODO(caseq): entry time is clamped; consider exposing an
// unclamped time.
.setTime(ToProtocolTime(timeOrigin, entry->startTime()))
.build();
if (entry->duration())
result->setDuration(ConvertDOMHighResTimeStampToSeconds(entry->duration()));
if (auto* lcp = DynamicTo<LargestContentfulPaint>(entry))
result->setLcpDetails(BuildEventDetails(*lcp, timeOrigin));
else if (auto* ls = DynamicTo<LayoutShift>(entry))
result->setLayoutShiftDetails(BuildEventDetails(*ls, timeOrigin));
return result;
}
} // namespace
using protocol::Response;
InspectorPerformanceTimelineAgent::InspectorPerformanceTimelineAgent(
InspectedFrames* inspected_frames)
: inspected_frames_(inspected_frames),
enabled_types_(&agent_state_, /*default_value=*/false) {}
InspectorPerformanceTimelineAgent::~InspectorPerformanceTimelineAgent() =
default;
void InspectorPerformanceTimelineAgent::Trace(Visitor* visitor) const {
visitor->Trace(inspected_frames_);
InspectorBaseAgent<protocol::PerformanceTimeline::Metainfo>::Trace(visitor);
}
void InspectorPerformanceTimelineAgent::Restore() {
if (IsEnabled())
InnerEnable();
}
void InspectorPerformanceTimelineAgent::InnerEnable() {
DCHECK(IsEnabled());
instrumenting_agents_->AddInspectorPerformanceTimelineAgent(this);
}
void InspectorPerformanceTimelineAgent::PerformanceEntryAdded(
ExecutionContext* context,
PerformanceEntry* entry) {
if (!(entry->EntryTypeEnum() & enabled_types_.Get()))
return;
String frame_id;
Performance* performance = nullptr;
if (auto* window = DynamicTo<LocalDOMWindow>(context)) {
frame_id = IdentifiersFactory::FrameId(window->GetFrame());
performance = DOMWindowPerformance::performance(*window);
} else if (auto* global_scope = DynamicTo<WorkerGlobalScope>(context)) {
performance = WorkerGlobalScopePerformance::performance(*global_scope);
} else {
NOTREACHED() << "Unexpected subtype of ExecutionContext";
}
GetFrontend()->timelineEventAdded(
BuildProtocolEvent(frame_id, performance->timeOrigin(), entry));
}
protocol::Response InspectorPerformanceTimelineAgent::enable(
std::unique_ptr<protocol::Array<String>> entry_types) {
EventsVector buffered_events;
const int old_types = enabled_types_.Get();
PerformanceEntryType new_types = 0;
for (const auto& type_str : *entry_types) {
AtomicString type_atomic(type_str);
PerformanceEntryType type_enum =
PerformanceEntry::ToEntryTypeEnum(type_atomic);
if (type_enum == PerformanceEntry::EntryType::kInvalid ||
(type_enum & kSupportedTypes) != type_enum) {
return Response::InvalidParams("Unknown or unsupported entry type");
}
// Gather buffered entries for types that haven't been enabled previously
// (but disregard duplicate type specifiers).
if (!(old_types & type_enum) && !(new_types & type_enum))
CollectEntries(type_atomic, &buffered_events);
new_types |= type_enum;
}
enabled_types_.Set(new_types);
if (!old_types != !new_types) {
if (!new_types)
return disable();
InnerEnable();
}
for (auto& event : buffered_events)
GetFrontend()->timelineEventAdded(std::move(event));
return Response::Success();
}
protocol::Response InspectorPerformanceTimelineAgent::disable() {
enabled_types_.Clear();
instrumenting_agents_->RemoveInspectorPerformanceTimelineAgent(this);
return Response::Success();
}
bool InspectorPerformanceTimelineAgent::IsEnabled() const {
return !!enabled_types_.Get();
}
void InspectorPerformanceTimelineAgent::CollectEntries(AtomicString type,
EventsVector* events) {
for (LocalFrame* frame : *inspected_frames_) {
String frame_id = IdentifiersFactory::FrameId(frame);
LocalDOMWindow* window = frame->DomWindow();
if (!window)
continue;
WindowPerformance* performance = DOMWindowPerformance::performance(*window);
for (Member<PerformanceEntry> entry :
performance->getBufferedEntriesByType(type)) {
events->push_back(
BuildProtocolEvent(frame_id, performance->timeOrigin(), entry));
}
}
}
} // namespace blink