Commit 5288a856 authored by Andrey Kosyakov's avatar Andrey Kosyakov Committed by Chromium LUCI CQ

Expose LCP through DevTools protocol

- introduce experimental PerformanceTimeline domain;
- expose LargestContentfulPaint performance event through the above domain;

(moar event types are coming)

Doc: https://docs.google.com/document/d/180kO9C3yWs5uWuopmCuDjNtGVUNkWY8el07-tyGW7Yk
Bug: 1138441
Change-Id: Ie664ef93a609ba6e6f9dcdebd33136f3da5fd8a8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2606677
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarNicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#840500}
parent ce2f20c6
...@@ -7143,6 +7143,51 @@ domain Performance ...@@ -7143,6 +7143,51 @@ domain Performance
# Timestamp title. # Timestamp title.
string title string title
# Reporting of performance timeline events, as specified in
# https://w3c.github.io/performance-timeline/#dom-performanceobserver.
experimental domain PerformanceTimeline
depends on DOM
depends on Network
# See https://github.com/WICG/LargestContentfulPaint and largest_contentful_paint.idl
type LargestContentfulPaint extends object
properties
number renderTime
number loadTime
# The number of pixels being painted.
number size
# The id attribute of the element, if available.
optional string elementId
# The URL of the image (may be trimmed).
optional string url
optional DOM.BackendNodeId nodeId
type TimelineEvent extends object
properties
# Identifies the frame that this event is related to. Empty for non-frame targets.
Page.FrameId frameId
string type
string name
# Time in seconds since Epoch, monotonically increasing within document lifetime.
Network.TimeSinceEpoch time
# Event duration, if applicable.
optional number duration
optional LargestContentfulPaint lcpDetails
# Previously buffered events would be reported before method returns.
# The specified filter overrides any previous filters, passing empty
# filter disables recording.
# Note that not all types exposed to the web platform are currently supported.
# See also: timelineEventAdded
command enable
parameters
array of string eventTypes
# Sent when a performance timeline event is added. See reportPerformanceTimeline method.
event timelineEventAdded
parameters
TimelineEvent event
# Security # Security
domain Security domain Security
......
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
#include "third_party/blink/renderer/core/inspector/inspector_overlay_agent.h" #include "third_party/blink/renderer/core/inspector/inspector_overlay_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_page_agent.h" #include "third_party/blink/renderer/core/inspector/inspector_page_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_performance_agent.h" #include "third_party/blink/renderer/core/inspector/inspector_performance_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_performance_timeline_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_resource_container.h" #include "third_party/blink/renderer/core/inspector/inspector_resource_container.h"
#include "third_party/blink/renderer/core/inspector/inspector_resource_content_loader.h" #include "third_party/blink/renderer/core/inspector/inspector_resource_content_loader.h"
#include "third_party/blink/renderer/core/inspector/inspector_task_runner.h" #include "third_party/blink/renderer/core/inspector/inspector_task_runner.h"
...@@ -288,6 +289,9 @@ void WebDevToolsAgentImpl::AttachSession(DevToolsSession* session, ...@@ -288,6 +289,9 @@ void WebDevToolsAgentImpl::AttachSession(DevToolsSession* session,
session->Append(MakeGarbageCollected<InspectorEmulationAgent>( session->Append(MakeGarbageCollected<InspectorEmulationAgent>(
web_local_frame_impl_.Get())); web_local_frame_impl_.Get()));
session->Append(MakeGarbageCollected<InspectorPerformanceTimelineAgent>(
inspected_frames));
// Call session init callbacks registered from higher layers. // Call session init callbacks registered from higher layers.
CoreInitializer::GetInstance().InitInspectorAgentSession( CoreInitializer::GetInstance().InitInspectorAgentSession(
session, include_view_agents_, dom_agent, inspected_frames, session, include_view_agents_, dom_agent, inspected_frames,
......
...@@ -77,6 +77,8 @@ inspector_protocol_generate("protocol_sources") { ...@@ -77,6 +77,8 @@ inspector_protocol_generate("protocol_sources") {
"inspector/protocol/Page.h", "inspector/protocol/Page.h",
"inspector/protocol/Performance.cpp", "inspector/protocol/Performance.cpp",
"inspector/protocol/Performance.h", "inspector/protocol/Performance.h",
"inspector/protocol/PerformanceTimeline.cpp",
"inspector/protocol/PerformanceTimeline.h",
"inspector/protocol/Protocol.cpp", "inspector/protocol/Protocol.cpp",
"inspector/protocol/Protocol.h", "inspector/protocol/Protocol.h",
"inspector/protocol/Runtime.h", "inspector/protocol/Runtime.h",
......
...@@ -77,6 +77,8 @@ blink_core_sources_inspector = [ ...@@ -77,6 +77,8 @@ blink_core_sources_inspector = [
"inspector_page_agent.h", "inspector_page_agent.h",
"inspector_performance_agent.cc", "inspector_performance_agent.cc",
"inspector_performance_agent.h", "inspector_performance_agent.h",
"inspector_performance_timeline_agent.cc",
"inspector_performance_timeline_agent.h",
"inspector_resource_container.cc", "inspector_resource_container.cc",
"inspector_resource_container.h", "inspector_resource_container.h",
"inspector_resource_content_loader.cc", "inspector_resource_content_loader.cc",
......
// 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/worker_global_scope_performance.h"
namespace blink {
namespace {
constexpr PerformanceEntryType kSupportedTypes =
PerformanceEntry::EntryType::kLargestContentfulPaint;
std::unique_ptr<protocol::PerformanceTimeline::LargestContentfulPaint>
BuildEventDetails(LargestContentfulPaint* lcp, DOMHighResTimeStamp timeOrigin) {
const double renderTime =
lcp->renderTime()
? ConvertDOMHighResTimeStampToSeconds(timeOrigin + lcp->renderTime())
: 0;
const double loadTime =
lcp->loadTime()
? ConvertDOMHighResTimeStampToSeconds(timeOrigin + lcp->loadTime())
: 0;
auto result = protocol::PerformanceTimeline::LargestContentfulPaint::create()
.setRenderTime(renderTime)
.setLoadTime(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::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(ConvertDOMHighResTimeStampToSeconds(
timeOrigin + entry->startTime()))
.build();
if (entry->duration())
result->setDuration(ConvertDOMHighResTimeStampToSeconds(entry->duration()));
if (auto* lcp = DynamicTo<LargestContentfulPaint>(entry))
result->setLcpDetails(BuildEventDetails(lcp, 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
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INSPECTOR_PERFORMANCE_TIMELINE_AGENT_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INSPECTOR_PERFORMANCE_TIMELINE_AGENT_H_
#include <memory>
#include "base/macros.h"
#include "base/task/sequence_manager/task_time_observer.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/inspector/inspector_base_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_session_state.h"
#include "third_party/blink/renderer/core/inspector/protocol/PerformanceTimeline.h"
namespace blink {
class ExecutionContext;
class InspectedFrames;
class PerformanceEntry;
class CORE_EXPORT InspectorPerformanceTimelineAgent final
: public InspectorBaseAgent<protocol::PerformanceTimeline::Metainfo> {
public:
explicit InspectorPerformanceTimelineAgent(InspectedFrames*);
~InspectorPerformanceTimelineAgent() override;
// PerformanceTimeline probes implementation.
void PerformanceEntryAdded(ExecutionContext*, PerformanceEntry*);
void Trace(Visitor*) const override;
private:
// Performance protocol domain implementation.
protocol::Response enable(
std::unique_ptr<protocol::Array<String>> event_types) override;
protocol::Response disable() override;
void Restore() override;
void InnerEnable();
bool IsEnabled() const;
using EventsVector =
protocol::Array<protocol::PerformanceTimeline::TimelineEvent>;
void CollectEntries(AtomicString type, EventsVector* events);
Member<InspectedFrames> inspected_frames_;
InspectorAgentState::Integer enabled_types_;
DISALLOW_COPY_AND_ASSIGN(InspectorPerformanceTimelineAgent);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INSPECTOR_PERFORMANCE_TIMELINE_AGENT_H_
...@@ -81,6 +81,9 @@ ...@@ -81,6 +81,9 @@
{ {
"domain": "Performance" "domain": "Performance"
}, },
{
"domain": "PerformanceTimeline"
},
{ {
"domain": "Emulation", "domain": "Emulation",
"include": ["forceViewport", "resetViewport", "resetPageScaleFactor", "setPageScaleFactor", "setScriptExecutionDisabled", "setTouchEmulationEnabled", "include": ["forceViewport", "resetViewport", "resetPageScaleFactor", "setPageScaleFactor", "setScriptExecutionDisabled", "setTouchEmulationEnabled",
......
...@@ -236,6 +236,11 @@ ...@@ -236,6 +236,11 @@
"DidFinishDebuggerTask", "DidFinishDebuggerTask",
] ]
}, },
InspectorPerformanceTimelineAgent: {
probes: [
"PerformanceEntryAdded",
]
},
InspectorTraceEvents: { InspectorTraceEvents: {
probes: [ probes: [
"CallFunction", "CallFunction",
......
...@@ -61,6 +61,7 @@ interface CoreProbes { ...@@ -61,6 +61,7 @@ interface CoreProbes {
class XMLHttpRequest; class XMLHttpRequest;
class HTMLInputElement; class HTMLInputElement;
class HTMLPortalElement; class HTMLPortalElement;
class PerformanceEntry;
void DidClearDocumentOfWindowObject([Keep] LocalFrame*); void DidClearDocumentOfWindowObject([Keep] LocalFrame*);
void WillInsertDOMNode([Keep] Node* parent); void WillInsertDOMNode([Keep] Node* parent);
...@@ -189,4 +190,5 @@ interface CoreProbes { ...@@ -189,4 +190,5 @@ interface CoreProbes {
void GetDisabledImageTypes(ExecutionContext*, HashSet<String>* result); void GetDisabledImageTypes(ExecutionContext*, HashSet<String>* result);
void OnContentSecurityPolicyViolation(ExecutionContext* context, const blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType violationType); void OnContentSecurityPolicyViolation(ExecutionContext* context, const blink::ContentSecurityPolicy::ContentSecurityPolicyViolationType violationType);
void IsCacheDisabled(ExecutionContext*, bool* is_cache_disabled); void IsCacheDisabled(ExecutionContext*, bool* is_cache_disabled);
void PerformanceEntryAdded([Keep] ExecutionContext*, PerformanceEntry*);
} }
...@@ -51,6 +51,14 @@ class CORE_EXPORT LargestContentfulPaint final : public PerformanceEntry { ...@@ -51,6 +51,14 @@ class CORE_EXPORT LargestContentfulPaint final : public PerformanceEntry {
WeakMember<Element> element_; WeakMember<Element> element_;
}; };
template <>
struct DowncastTraits<LargestContentfulPaint> {
static bool AllowFrom(const PerformanceEntry& entry) {
return entry.EntryTypeEnum() ==
PerformanceEntry::EntryType::kLargestContentfulPaint;
}
};
} // namespace blink } // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_LARGEST_CONTENTFUL_PAINT_H_ #endif // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_LARGEST_CONTENTFUL_PAINT_H_
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
#include "third_party/blink/renderer/core/inspector/console_message.h" #include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/document_load_timing.h" #include "third_party/blink/renderer/core/loader/document_load_timing.h"
#include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/timing/largest_contentful_paint.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.h"
#include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h" #include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h"
...@@ -637,6 +638,7 @@ void Performance::AddLayoutShiftBuffer(LayoutShift& entry) { ...@@ -637,6 +638,7 @@ void Performance::AddLayoutShiftBuffer(LayoutShift& entry) {
} }
void Performance::AddLargestContentfulPaint(LargestContentfulPaint* entry) { void Performance::AddLargestContentfulPaint(LargestContentfulPaint* entry) {
probe::PerformanceEntryAdded(GetExecutionContext(), entry);
if (largest_contentful_paint_buffer_.size() < if (largest_contentful_paint_buffer_.size() <
kDefaultLargestContenfulPaintSize) { kDefaultLargestContenfulPaintSize) {
largest_contentful_paint_buffer_.push_back(entry); largest_contentful_paint_buffer_.push_back(entry);
......
Basic test for LargestContentfulPaint support in PerformanceTimeline
[
[0] : {
frameId : <string>
lcpDetails : {
elementId : text
loadTime : <number>
nodeId : <div#text>
renderTime : <number>
size : 48000
}
name :
time : <number>
type : largest-contentful-paint
}
[1] : {
frameId : <string>
lcpDetails : {
elementId : image
loadTime : <number>
nodeId : <img#image>
renderTime : <number>
size : 65536
url : .../images/resources/green-256x256.jpg
}
name :
time : <number>
type : largest-contentful-paint
}
]
(async function(testRunner) {
const {page, session, dp} = await testRunner.startBlank('Basic test for LargestContentfulPaint support in PerformanceTimeline');
const unstableFields = ['time', 'renderTime', 'loadTime', 'frameId'];
const events = [];
const startTime = Date.now();
await dp.PerformanceTimeline.enable({eventTypes: ['largest-contentful-paint']});
dp.PerformanceTimeline.onTimelineEventAdded(event => events.push(event.params.event));
session.navigate(testRunner.url('resources/lcp.html'));
await dp.PerformanceTimeline.onceTimelineEventAdded();
session.evaluate(`
const img = document.createElement("img");
img.id = "image";
img.src = "${testRunner.url('../../images/resources/green-256x256.jpg')}";
document.body.appendChild(img);
`);
await dp.PerformanceTimeline.onceTimelineEventAdded();
const endTime = Date.now();
for (const event of events) {
checkTime(event.time);
if (event.lcpDetails.renderTime)
checkTime(event.lcpDetails.renderTime);
if (event.lcpDetails.loadTime)
checkTime(event.lcpDetails.loadTime);
await patchFields(event.lcpDetails);
}
testRunner.log(events, null, unstableFields);
testRunner.completeTest();
function checkTime(time) {
const timeInMs = time * 1000;
if (timeInMs < startTime || timeInMs > endTime)
testRunner.log(`FAIL: event time out of bounds, expect ${startTime} <= ${timeInMs} <= ${endTime}`);
}
async function patchFields(object) {
if (object.url)
object.url = testRunner.trimURL(object.url);
if (object.nodeId)
object.nodeId = await describeNode(object.nodeId);
}
async function describeNode(nodeId) {
const response = await dp.DOM.resolveNode({backendNodeId: nodeId});
return response.result && response.result.object.description ?
`<${response.result.object.description}>` : '<invalid id>';
}
})
<!DOCTYPE html>
<meta charset="utf-8">
<title>LCP Test Page</title>
<style>
body {
font: 10px Ahem;
}
</style>
<body>
<script src="/resources/ahem.js"></script>
<div id="text" style="width: 400px;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</body>
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