blob: d1298ce2d46974c3548afba7d64fd40044fc5937 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/performance.h"
#include <string>
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "cobalt/browser/stack_size_constants.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/memory_info.h"
#include "cobalt/dom/performance_entry.h"
#include "cobalt/dom/performance_mark.h"
#include "cobalt/dom/performance_measure.h"
namespace cobalt {
namespace dom {
namespace {
base::TimeDelta GetUnixAtZeroMonotonic(const base::Clock* clock,
const base::TickClock* tick_clock) {
base::TimeDelta unix_time_now = clock->Now() - base::Time::UnixEpoch();
base::TimeDelta time_since_origin = tick_clock->NowTicks().since_origin();
return unix_time_now - time_since_origin;
}
bool IsNamePerformanceTimingAttribute(const std::string& name) {
return name == "navigationStart";
}
DOMHighResTimeStamp ConvertNameToTimestamp(
const std::string& name, script::ExceptionState* exception_state) {
// The algorithm of ConvertNameToTimestamp() follows these steps:
// https://www.w3.org/TR/user-timing/#convert-a-name-to-a-timestamp
// 1. If the global object is not a Window object, throw a SyntaxError.
// 2. If name is navigationStart, return 0.
if (name == "navigationStart") {
return 0.0;
}
// 3. Let startTime be the value of navigationStart in the PerformanceTiming
// interface.
// 4. Let endTime be the value of name in the PerformanceTiming interface.
// 5. If endTime is 0, throw an InvalidAccessError.
// 6. Return result of subtracting startTime from endTime.
// Note that we only support navigationStart in the PerformanceTiming
// interface. We return 0.0 instead of the result of subtracting
// startTime from endTime.
dom::DOMException::Raise(dom::DOMException::kSyntaxErr,
"Cannot convert a name that is not a public "
"attribute of PerformanceTiming to a timestamp",
exception_state);
return 0.0;
}
} // namespace
Performance::Performance(script::EnvironmentSettings* settings,
const scoped_refptr<base::BasicClock>& clock)
: EventTarget(settings),
time_origin_(base::TimeTicks::Now()),
tick_clock_(base::DefaultTickClock::GetInstance()),
timing_(new PerformanceTiming(
clock,
(time_origin_ - base::TimeTicks::UnixEpoch()).InMilliseconds())),
memory_(new MemoryInfo()),
resource_timing_buffer_size_limit_(
Performance::kMaxResourceTimingBufferSize),
resource_timing_buffer_current_size_(0),
resource_timing_buffer_full_event_pending_flag_(false),
resource_timing_secondary_buffer_current_size_(0),
performance_observer_task_queued_flag_(false),
add_to_performance_entry_buffer_flag_(false) {
unix_at_zero_monotonic_ =
GetUnixAtZeroMonotonic(base::DefaultClock::GetInstance(), tick_clock_);
lifecycle_timing_ = base::MakeRefCounted<PerformanceLifecycleTiming>(
"lifecycle timing", time_origin());
// Queue lifecycle timing.
QueuePerformanceEntry(lifecycle_timing_);
// Add lifecycle timing to the performance entry buffer.
performance_entry_buffer_.push_back(lifecycle_timing_);
}
// static
DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp(
base::TimeTicks time_origin, base::TimeTicks monotonic_time) {
if (monotonic_time.is_null() || time_origin.is_null()) return 0.0;
DOMHighResTimeStamp clamped_time =
ClampTimeStampMinimumResolution(
monotonic_time,
Performance::kPerformanceTimerMinResolutionInMicroseconds) -
ClampTimeStampMinimumResolution(
time_origin,
Performance::kPerformanceTimerMinResolutionInMicroseconds);
return clamped_time;
}
DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp(
base::TimeTicks monotonic_time) const {
return Performance::MonotonicTimeToDOMHighResTimeStamp(time_origin_,
monotonic_time);
}
DOMHighResTimeStamp Performance::Now() const {
// Now stores the current high resolution time.
// https://www.w3.org/TR/2019/REC-hr-time-2-20191121/#dfn-current-high-resolution-time
return MonotonicTimeToDOMHighResTimeStamp(tick_clock_->NowTicks());
}
scoped_refptr<PerformanceTiming> Performance::timing() const { return timing_; }
scoped_refptr<MemoryInfo> Performance::memory() const { return memory_; }
DOMHighResTimeStamp Performance::time_origin() const {
// The algorithm for calculating time origin timestamp.
// https://www.w3.org/TR/2019/REC-hr-time-2-20191121/#dfn-time-origin-timestamp
// Assert that global's time origin is not undefined.
DCHECK(!time_origin_.is_null());
// Let t1 be the DOMHighResTimeStamp representing the high resolution
// time at which the global monotonic clock is zero.
base::TimeDelta t1 = unix_at_zero_monotonic_;
// Let t2 be the DOMHighResTimeStamp representing the high resolution
// time value of the global monotonic clock at global's time origin.
base::TimeDelta t2 = time_origin_ - base::TimeTicks();
// Return the sum of t1 and t2.
return ClampTimeStampMinimumResolution(
t1 + t2, Performance::kPerformanceTimerMinResolutionInMicroseconds);
}
void Performance::Mark(const std::string& mark_name,
script::ExceptionState* exception_state) {
// The algorithm for mark() follows these steps:
// https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#mark-method
// 1. If the global object is a Window object and markName uses the same name
// as a read only attribute in the PerformanceTiming interface, throw a
// SyntaxError.
if (IsNamePerformanceTimingAttribute(mark_name)) {
dom::DOMException::Raise(
dom::DOMException::kSyntaxErr,
"Cannot create a mark with the same name as a read-only attribute in "
"the PerformanceTiming interface",
exception_state);
}
// 2. Create a new PerformanceMark object (entry).
// 3. Set entry's name attribute to markName.
// 4. Set entry's entryType attribute to DOMString "mark".
// 5. Set entry's startTime attribute to the value that would be returned by
// the Performance object's now() method.
// 6. Set entry's duration attribute to 0.
scoped_refptr<PerformanceMark> entry =
base::MakeRefCounted<PerformanceMark>(mark_name, Now());
// 7. Queue entry.
QueuePerformanceEntry(entry);
// 8. Add entry to the performance entry buffer.
performance_entry_buffer_.push_back(entry);
// 9. Return undefined
}
void Performance::ClearMarks(const std::string& mark_name) {
// The algorithm for clearMarks follows these steps:
// https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#clearmarks-method
// 1. If markName is omitted, remove all PerformanceMark objects from the
// performance entry buffer.
// 2. Otherwise, remove all PerformanceMark objects listed in the performance
// entry buffer whose name matches markName.
PerformanceEntryList retained_performance_entry_buffer;
for (const auto& entry : performance_entry_buffer_) {
bool should_remove_entry =
PerformanceEntry::ToEntryTypeEnum(entry->entry_type()) ==
PerformanceEntry::kMark &&
(mark_name.empty() || entry->name() == mark_name);
if (!should_remove_entry) {
retained_performance_entry_buffer.push_back(entry);
}
}
performance_entry_buffer_.swap(retained_performance_entry_buffer);
// 3. Return undefined.
}
void Performance::Measure(const std::string& measure_name,
const std::string& start_mark,
const std::string& end_mark,
script::ExceptionState* exception_state) {
// The algorithm for measure() follows these steps:
// https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#measure-method
// 1. Let end time be 0.
DOMHighResTimeStamp end_time = 0.0;
// 2. If endMark is omitted, let end time be the value that would be returned
// by the Performance object's now() method.
if (end_mark.empty()) {
end_time = Now();
} else if (IsNamePerformanceTimingAttribute(end_mark)) {
// 2.1. Otherwise, if endMark has the same name as a read only attribute in
// the PerformanceTiming interface, let end time be the value returned by
// running the convert a name to a timestamp algorithm with name set to the
// value of endMark.
end_time = ConvertNameToTimestamp(end_mark, exception_state);
} else {
// 2.2. Otherwise let end time be the value of the startTime attribute from
// the most recent occurrence of a PerformanceMark object in the performance
// entry buffer whose name matches the value of endMark. If no matching
// entry is found, throw a SyntaxError.
PerformanceEntryList list = GetEntriesByName(end_mark, "mark");
if (list.empty()) {
dom::DOMException::Raise(
dom::DOMException::kSyntaxErr,
"Cannot create measure; no mark found with name: " + end_mark + ".",
exception_state);
return;
}
end_time = list.at(list.size() - 1)->start_time();
}
DOMHighResTimeStamp start_time;
// 3. If startMark is omitted, let start time be 0.
if (start_mark.empty()) {
start_time = 0.0;
} else if (IsNamePerformanceTimingAttribute(start_mark)) {
// 3.1. If startMark has the same name as a read only attribute in the
// PerformanceTiming interface, let start time be the value returned by
// running the convert a name to a timestamp algorithm with name set to
// startMark.
start_time = ConvertNameToTimestamp(start_mark, exception_state);
} else {
// 3.2. Otherwise let start time be the value of the startTime attribute
// from the most recent occurrence of a PerformanceMark object in the
// performance entry buffer whose name matches the value of startMark. If no
// matching entry is found, throw a SyntaxError.
PerformanceEntryList list = GetEntriesByName(start_mark, "mark");
if (list.empty()) {
dom::DOMException::Raise(
dom::DOMException::kSyntaxErr,
"Cannot create measure; no mark found with name: " + start_mark + ".",
exception_state);
return;
}
start_time = list.at(list.size() - 1)->start_time();
}
// 4. Create a new PerformanceMeasure object (entry).
// 5. Set entry's name attribute to measureName.
// 6. Set entry's entryType attribute to DOMString "measure".
// 7. Set entry's startTime attribute to start time.
// 8. Set entry's duration attribute to the duration from start time to end
// time. The resulting duration value MAY be negative.
scoped_refptr<PerformanceMeasure> entry =
base::MakeRefCounted<PerformanceMeasure>(measure_name, start_time,
end_time);
// 9. Queue entry.
QueuePerformanceEntry(entry);
// 10. Add entry to the performance entry buffer.
performance_entry_buffer_.push_back(entry);
// 11. Return undefined.
}
void Performance::ClearMeasures(const std::string& measure_name) {
// The algorithm for clearMeasures follows these steps:
// https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#clearmeasures-method
// 1. If measureName is omitted, remove all PerformanceMeasure objects in the
// performance entry buffer.
// 2. Otherwise remove all PerformanceMeasure objects listed in the
// performance entry buffer whose name matches measureName.
PerformanceEntryList performance_entry_buffer;
for (const auto& entry : performance_entry_buffer_) {
bool shouldRemoveEntry =
PerformanceEntry::ToEntryTypeEnum(entry->entry_type()) ==
PerformanceEntry::kMeasure &&
(measure_name.empty() || entry->name() == measure_name);
if (!shouldRemoveEntry) {
performance_entry_buffer.push_back(entry);
}
}
performance_entry_buffer_.swap(performance_entry_buffer);
// 3. Return undefined.
}
void Performance::UnregisterPerformanceObserver(
const scoped_refptr<PerformanceObserver>& old_observer) {
auto iter = registered_performance_observers_.begin();
while (iter != registered_performance_observers_.end()) {
if (iter->observer == old_observer) {
iter = registered_performance_observers_.erase(iter);
} else {
++iter;
}
}
}
void Performance::RegisterPerformanceObserver(
const scoped_refptr<PerformanceObserver>& observer,
const PerformanceObserverInit& options) {
std::list<PerformanceObserverInit> options_list;
options_list.push_back(options);
registered_performance_observers_.emplace_back(observer, options_list);
}
void Performance::ReplaceRegisteredPerformanceObserverOptionsList(
const scoped_refptr<PerformanceObserver>& observer,
const PerformanceObserverInit& options) {
auto iter = registered_performance_observers_.begin();
while (iter != registered_performance_observers_.end()) {
if (iter->observer == observer) {
iter->options_list.clear();
iter->options_list.push_back(options);
}
++iter;
}
}
void Performance::UpdateRegisteredPerformanceObserverOptionsList(
const scoped_refptr<PerformanceObserver>& observer,
const PerformanceObserverInit& options) {
auto iter = registered_performance_observers_.begin();
while (iter != registered_performance_observers_.end()) {
if (iter->observer == observer) {
bool is_replaced = false;
for (auto& registered_options : iter->options_list) {
if (registered_options.type() == options.type()) {
registered_options = options;
is_replaced = true;
}
}
if (!is_replaced) iter->options_list.push_back(options);
}
++iter;
}
}
void Performance::TraceMembers(script::Tracer* tracer) {
tracer->Trace(timing_);
tracer->Trace(memory_);
tracer->Trace(lifecycle_timing_);
}
PerformanceEntryList Performance::GetEntries() {
return PerformanceEntryListImpl::GetEntries(performance_entry_buffer_);
}
PerformanceEntryList Performance::GetEntriesByType(
const std::string& entry_type) {
return PerformanceEntryListImpl::GetEntriesByType(performance_entry_buffer_,
entry_type);
}
PerformanceEntryList Performance::GetEntriesByName(
const std::string& name, const base::StringPiece& type) {
return PerformanceEntryListImpl::GetEntriesByName(performance_entry_buffer_,
name, type);
}
void Performance::ClearResourceTimings() {
// The method clearResourceTimings runs the following steps:
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dom-performance-clearresourcetimings
// 1. Remove all PerformanceResourceTiming objects in the performance
// entry buffer.
PerformanceEntryList performance_entry_buffer;
for (const auto& entry : performance_entry_buffer_) {
bool should_be_removed =
PerformanceEntry::ToEntryTypeEnum(entry->entry_type()) ==
PerformanceEntry::kResource;
if (!should_be_removed) {
performance_entry_buffer.push_back(entry);
}
}
performance_entry_buffer_.swap(performance_entry_buffer);
// 2. Set resource timing buffer current size to 0.
resource_timing_buffer_current_size_ = 0;
}
void Performance::SetResourceTimingBufferSize(
unsigned long max_size) { // NOLINT(runtime/int)
// The method runs the following steps:
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dom-performance-setresourcetimingbuffersize
// 1. Set resource timing buffer size limit to the maxSize parameter.
// If the maxSize parameter is less than resource timing buffer current
// size, no PerformanceResourceTiming objects are to be removed from
// the performance entry buffer.
resource_timing_buffer_size_limit_ = max_size;
}
bool Performance::CanAddResourceTimingEntry() {
// THe method runs the following steps:
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dfn-can-add-resource-timing-entry
// 1. If resource timing buffer current size is smaller than resource
// timing buffer size limit, return true.
// 2. Return false.
return resource_timing_buffer_current_size_ <
resource_timing_buffer_size_limit_;
}
void Performance::CopySecondaryBuffer() {
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dfn-copy-secondary-buffer
// While resource timing secondary buffer is not empty and can add
// resource timing entry returns true, run the following substeps:
PerformanceEntryList entry_list;
while (!resource_timing_secondary_buffer_.empty() &&
CanAddResourceTimingEntry()) {
// 1. Let entry be the oldest PerformanceResourceTiming in resource timing
// secondary buffer.
scoped_refptr<PerformanceResourceTiming> entry =
resource_timing_secondary_buffer_.front();
// 2. Add entry to the end of performance entry buffer.
DCHECK(entry);
performance_entry_buffer_.push_back(entry);
// 3. Increment resource timing buffer current size by 1.
resource_timing_buffer_current_size_++;
// 4. Remove entry from resource timing secondary buffer.
resource_timing_secondary_buffer_.pop_front();
// 5. Decrement resource timing secondary buffer current size by 1.
resource_timing_secondary_buffer_current_size_--;
}
}
void Performance::set_onresourcetimingbufferfull(
const EventTarget::EventListenerScriptValue& event_listener) {
SetAttributeEventListener(base::Tokens::resourcetimingbufferfull(),
event_listener);
}
const EventTarget::EventListenerScriptValue*
Performance::onresourcetimingbufferfull() const {
return GetAttributeEventListener(base::Tokens::resourcetimingbufferfull());
}
void Performance::FireResourceTimingBufferFullEvent() {
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dfn-fire-a-buffer-full-event
// 1. While resource timing secondary buffer is not empty, run the
// following substeps:
while (!resource_timing_secondary_buffer_.empty()) {
// 1.1 Let number of excess entries before be resource timing secondary
// buffer current size.
unsigned long excess_entries_before = // NOLINT(runtime/int)
resource_timing_secondary_buffer_current_size_;
// 1.2 If can add resource timing entry returns false, then fire an event
// named resourcetimingbufferfull at the Performance object.
if (!CanAddResourceTimingEntry()) {
DispatchEvent(new Event(base::Tokens::resourcetimingbufferfull()));
}
// 1.3 Run copy secondary buffer.
CopySecondaryBuffer();
// 1.4 Let number of excess entries after be resource timing secondary
// buffer current size.
unsigned long excess_entries_after = // NOLINT(runtime/int)
resource_timing_secondary_buffer_current_size_;
// 1.5 If number of excess entries before is lower than or equals number of
// excess entries after, then remove all entries from resource timing
// secondary buffer, set resource timing secondary buffer current size to 0,
// and abort these steps.
if (excess_entries_before <= excess_entries_after) {
resource_timing_secondary_buffer_.clear();
resource_timing_secondary_buffer_current_size_ = 0;
break;
}
}
// 2. Set resource timing buffer full event pending flag to false.
resource_timing_buffer_full_event_pending_flag_ = false;
}
void Performance::AddPerformanceResourceTimingEntry(
const scoped_refptr<PerformanceResourceTiming>& resource_timing_entry) {
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dfn-add-a-performanceresourcetiming-entry
// To add a PerformanceResourceTiming entry into the performance entry buffer,
// run the following steps:
// 1. Let new entry be the input PerformanceEntry to be added.
// 2. If can add resource timing entry returns true and resource timing buffer
// full event pending flag is false, run the following substeps:
if (CanAddResourceTimingEntry() &&
!resource_timing_buffer_full_event_pending_flag_) {
// 2.1 Add new entry to the performance entry buffer.
performance_entry_buffer_.push_back(resource_timing_entry);
// 2.2 Increase resource timing buffer current size by 1.
resource_timing_buffer_current_size_++;
// 2.3 Return.
return;
}
// 3. If resource timing buffer full event pending flag is false, run the
// following substeps:
if (!resource_timing_buffer_full_event_pending_flag_) {
// 3.1 Set resource timing buffer full event pending flag to true.
resource_timing_buffer_full_event_pending_flag_ = true;
// 3.2 Queue a task on the performance timeline task source to run fire
// a buffer full event.
FireResourceTimingBufferFullEvent();
}
// 4. Add new entry to the resource timing secondary buffer.
resource_timing_secondary_buffer_.push_back(resource_timing_entry);
// 5. Increase resource timing secondary buffer current size by 1.
resource_timing_secondary_buffer_current_size_++;
DCHECK_EQ(resource_timing_secondary_buffer_current_size_,
resource_timing_secondary_buffer_.size());
}
void Performance::QueuePerformanceEntry(
const scoped_refptr<PerformanceEntry>& entry) {
// To queue a PerformanceEntry (new entry) with an optional add to
// performance entry buffer flag, which is unset by default, run these steps:
// https://www.w3.org/TR/2019/WD-performance-timeline-2-20191024/#queue-a-performanceentry
// 1. Let interested observers be an initially empty set of
// PerformanceObserver objects.
std::list<scoped_refptr<PerformanceObserver>> interested_observers;
// 2. Let entryType be new entry’s entryType value.
const std::string entry_type = entry->entry_type();
// 3. For each registered performance observer (regObs):
for (const auto& reg_obs : registered_performance_observers_) {
// 3.1 If regObs's options list contains a PerformanceObserverInit item
// whose entryTypes member include entryType or whose type member equals to
// entryType, append regObs's observer to interested observers.
for (const auto& item : reg_obs.options_list) {
if (item.has_type() && item.type() == entry_type) {
interested_observers.push_back(reg_obs.observer);
}
if (item.has_entry_types()) {
for (const auto& type : item.entry_types()) {
if (type == entry_type) {
interested_observers.push_back(reg_obs.observer);
}
}
}
}
}
// 4. For each observer in interested observers:
for (const auto& observer : interested_observers) {
// 4.1 Append new entry to observer's observer buffer.
observer->EnqueuePerformanceEntry(entry);
}
// 5. If the add to performance entry buffer flag is set, add new entry to the
// performance entry buffer.
if (add_to_performance_entry_buffer_flag_) {
performance_entry_buffer_.push_back(entry);
}
// 6. If the performance observer task queued flag is set, terminate these
// steps.
if (performance_observer_task_queued_flag_) return;
// 7. Set performance observer task queued flag.
performance_observer_task_queued_flag_ = true;
// 8. Queue a task that consists of running the following substeps.
// The task source for the queued task is the performance timeline task
// source.
QueuePerformanceTimelineTask();
}
void Performance::QueuePerformanceTimelineTask() {
// 8.1 Unset performance observer task queued flag for the relevant global
// object.
performance_observer_task_queued_flag_ = false;
// 8.2 Let notify list be a copy of relevant global object's list of
// registered performance observer objects.
RegisteredPerformanceObserverList notify_list =
registered_performance_observers_;
// 8.3 For each PerformanceObserver object po in notify list, run these steps:
for (const auto& reg_obs : notify_list) {
// 8.3.1 Let entries be a copy of po’s observer buffer.
scoped_refptr<PerformanceObserver> po = reg_obs.observer;
PerformanceEntryList entries = po->GetObserverBuffer();
// 8.3.2 Empty po’s observer buffer.
po->EmptyObserverBuffer();
// If entries is non-empty, call po’s callback with entries as first
// argument and po as the second argument and callback this value. If this
// throws an exception, report the exception.
if (!entries.empty()) {
scoped_refptr<PerformanceObserverEntryList> observer_entry_list(
new PerformanceObserverEntryList(entries));
po->GetPerformanceObserverCallback()->RunCallback(observer_entry_list,
po);
}
}
}
void Performance::CreatePerformanceResourceTiming(
const net::LoadTimingInfo& timing_info, const std::string& initiator_type,
const std::string& requested_url) {
// To mark resource timing given a fetch timing info timingInfo, a DOMString
// requestedURL, a DOMString initiatorType a global object global, and a
// string cacheMode, perform the following steps:
// https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#marking-resource-timing
// 1. Create a PerformanceResourceTiming object entry in global's realm.
// 2.Setup the resource timing entry for entry, given initiatorType,
// requestedURL, timingInfo, and cacheMode.
scoped_refptr<PerformanceResourceTiming> resource_timing(
new PerformanceResourceTiming(timing_info, initiator_type, requested_url,
this, time_origin_));
// 2. Queue entry.
QueuePerformanceEntry(resource_timing);
// 3. Add entry to global's performance entry buffer.
AddPerformanceResourceTimingEntry(resource_timing);
}
void Performance::SetApplicationState(base::ApplicationState state,
SbTimeMonotonic timestamp) {
lifecycle_timing_->SetApplicationState(state, timestamp);
}
void Performance::SetApplicationStartOrPreloadTimestamp(
bool is_preload, SbTimeMonotonic timestamp) {
lifecycle_timing_->SetApplicationStartOrPreloadTimestamp(is_preload,
timestamp);
}
void Performance::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
lifecycle_timing_->SetDeepLinkTimestamp(timestamp);
}
} // namespace dom
} // namespace cobalt