blob: 777788c818e2372271cd6ced2803774d489cdd94 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2012 Intel Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/timing/performance.h"
#include <algorithm>
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/renderer/bindings/core/v8/double_or_performance_mark_options.h"
#include "third_party/blink/renderer/bindings/core/v8/string_or_double_or_performance_measure_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_timing.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/use_counter.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/timing/performance_event_timing.h"
#include "third_party/blink/renderer/core/timing/performance_long_task_timing.h"
#include "third_party/blink/renderer/core/timing/performance_mark.h"
#include "third_party/blink/renderer/core/timing/performance_measure.h"
#include "third_party/blink/renderer/core/timing/performance_measure_options.h"
#include "third_party/blink/renderer/core/timing/performance_observer.h"
#include "third_party/blink/renderer/core/timing/performance_resource_timing.h"
#include "third_party/blink/renderer/core/timing/performance_user_timing.h"
#include "third_party/blink/renderer/core/timing/time_clamper.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
namespace {
const SecurityOrigin* GetSecurityOrigin(ExecutionContext* context) {
if (context)
return context->GetSecurityOrigin();
return nullptr;
}
// When a Performance object is first created, use the current system time
// to calculate what the Unix time would be at the time the monotonic clock time
// was zero, assuming no manual changes to the system clock. This can be
// calculated as current_unix_time - current_monotonic_time.
DOMHighResTimeStamp GetUnixAtZeroMonotonic() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
DOMHighResTimeStamp, unix_at_zero_monotonic,
{ConvertSecondsToDOMHighResTimeStamp(CurrentTime() -
CurrentTimeTicksInSeconds())});
return unix_at_zero_monotonic;
}
Performance::MeasureParameterType StringToNavigationTimingParameterType(
const String& s) {
// The following names come from performance_user_timing.cc.
if (s == "unloadEventStart")
return Performance::MeasureParameterType::kUnloadEventStart;
if (s == "unloadEventEnd")
return Performance::MeasureParameterType::kUnloadEventEnd;
if (s == "domInteractive")
return Performance::MeasureParameterType::kDomInteractive;
if (s == "domContentLoadedEventStart")
return Performance::MeasureParameterType::kDomContentLoadedEventStart;
if (s == "domContentLoadedEventEnd")
return Performance::MeasureParameterType::kDomContentLoadedEventEnd;
if (s == "domComplete")
return Performance::MeasureParameterType::kDomComplete;
if (s == "loadEventStart")
return Performance::MeasureParameterType::kLoadEventStart;
if (s == "loadEventEnd")
return Performance::MeasureParameterType::kLoadEventEnd;
if (s == "navigationStart")
return Performance::MeasureParameterType::kNavigationStart;
if (s == "redirectStart")
return Performance::MeasureParameterType::kRedirectStart;
if (s == "redirectEnd")
return Performance::MeasureParameterType::kRedirectEnd;
if (s == "fetchStart")
return Performance::MeasureParameterType::kFetchStart;
if (s == "domainLookupStart")
return Performance::MeasureParameterType::kDomainLookupStart;
if (s == "domainLookupEnd")
return Performance::MeasureParameterType::kDomainLookupEnd;
if (s == "connectStart")
return Performance::MeasureParameterType::kConnectStart;
if (s == "connectEnd")
return Performance::MeasureParameterType::kConnectEnd;
if (s == "secureConnectionStart")
return Performance::MeasureParameterType::kSecureConnectionStart;
if (s == "requestStart")
return Performance::MeasureParameterType::kRequestStart;
if (s == "responseStart")
return Performance::MeasureParameterType::kResponseStart;
if (s == "responseEnd")
return Performance::MeasureParameterType::kResponseEnd;
if (s == "domLoading")
return Performance::MeasureParameterType::kDomLoading;
return Performance::MeasureParameterType::kOther;
}
Performance::MeasureParameterType StartOrOptionsToParameterType(
const StringOrDoubleOrPerformanceMeasureOptions& start_or_options) {
if (start_or_options.IsString()) {
return StringToNavigationTimingParameterType(
start_or_options.GetAsString());
}
if (start_or_options.IsDouble())
return Performance::MeasureParameterType::kNumber;
if (start_or_options.IsPerformanceMeasureOptions())
return Performance::MeasureParameterType::kObjectObject;
// null and undefined are undistinguishable in
// StringOrDoubleOrPerformanceMeasureOptions.
return Performance::MeasureParameterType::kUndefinedOrNull;
}
Performance::MeasureParameterType EndToParameterType(
const StringOrDouble& end) {
if (end.IsString()) {
// When passing an object to |end|, the object will be implicitly converted
// as the following string.
if (end.GetAsString() == "[object Object]")
return Performance::MeasureParameterType::kObjectObject;
return StringToNavigationTimingParameterType(end.GetAsString());
}
if (end.IsDouble())
return Performance::MeasureParameterType::kNumber;
// null and undefined are undistinguishable in StringOrDouble.
return Performance::MeasureParameterType::kUndefinedOrNull;
}
void LogMeasureStartToUma(Performance::MeasureParameterType type) {
UMA_HISTOGRAM_ENUMERATION("Performance.MeasureParameter.StartMark", type);
}
void LogMeasureEndToUma(Performance::MeasureParameterType type) {
UMA_HISTOGRAM_ENUMERATION("Performance.MeasureParameter.EndMark", type);
}
} // namespace
using PerformanceObserverVector = HeapVector<Member<PerformanceObserver>>;
static const size_t kDefaultResourceTimingBufferSize = 250;
constexpr size_t kDefaultEventTimingBufferSize = 150;
Performance::Performance(
TimeTicks time_origin,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: resource_timing_buffer_size_(kDefaultResourceTimingBufferSize),
event_timing_buffer_max_size_(kDefaultEventTimingBufferSize),
user_timing_(nullptr),
time_origin_(time_origin),
observer_filter_options_(PerformanceEntry::kInvalid),
deliver_observations_timer_(std::move(task_runner),
this,
&Performance::DeliverObservationsTimerFired) {
}
Performance::~Performance() = default;
const AtomicString& Performance::InterfaceName() const {
return event_target_names::kPerformance;
}
PerformanceTiming* Performance::timing() const {
return nullptr;
}
PerformanceNavigation* Performance::navigation() const {
return nullptr;
}
MemoryInfo* Performance::memory() const {
return nullptr;
}
bool Performance::shouldYield() const {
return false;
}
DOMHighResTimeStamp Performance::timeOrigin() const {
DCHECK(!time_origin_.is_null());
return GetUnixAtZeroMonotonic() +
ConvertTimeTicksToDOMHighResTimeStamp(time_origin_);
}
PerformanceEntryVector Performance::getEntries() {
PerformanceEntryVector entries;
entries.AppendVector(resource_timing_buffer_);
entries.AppendVector(event_timing_buffer_);
if (first_input_timing_)
entries.push_back(first_input_timing_);
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
// This extra checking is needed when WorkerPerformance
// calls this method.
if (navigation_timing_)
entries.push_back(navigation_timing_);
if (user_timing_) {
entries.AppendVector(user_timing_->GetMarks());
entries.AppendVector(user_timing_->GetMeasures());
}
if (first_paint_timing_)
entries.push_back(first_paint_timing_);
if (first_contentful_paint_timing_)
entries.push_back(first_contentful_paint_timing_);
std::sort(entries.begin(), entries.end(),
PerformanceEntry::StartTimeCompareLessThan);
return entries;
}
PerformanceEntryVector Performance::getEntriesByType(
const AtomicString& entry_type) {
PerformanceEntryVector entries;
PerformanceEntry::EntryType type =
PerformanceEntry::ToEntryTypeEnum(entry_type);
switch (type) {
case PerformanceEntry::kResource:
for (const auto& resource : resource_timing_buffer_)
entries.push_back(resource);
break;
case PerformanceEntry::kEvent:
UseCounter::Count(GetExecutionContext(),
WebFeature::kEventTimingExplicitlyRequested);
for (const auto& event : event_timing_buffer_)
entries.push_back(event);
break;
case PerformanceEntry::kFirstInput:
UseCounter::Count(GetExecutionContext(),
WebFeature::kEventTimingExplicitlyRequested);
if (first_input_timing_)
entries.push_back(first_input_timing_);
break;
case PerformanceEntry::kNavigation:
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
if (navigation_timing_)
entries.push_back(navigation_timing_);
break;
case PerformanceEntry::kMark:
if (user_timing_)
entries.AppendVector(user_timing_->GetMarks());
break;
case PerformanceEntry::kMeasure:
if (user_timing_)
entries.AppendVector(user_timing_->GetMeasures());
break;
case PerformanceEntry::kPaint:
UseCounter::Count(GetExecutionContext(),
WebFeature::kPaintTimingRequested);
if (first_paint_timing_)
entries.push_back(first_paint_timing_);
if (first_contentful_paint_timing_)
entries.push_back(first_contentful_paint_timing_);
break;
// Unsupported for LongTask, TaskAttribution.
// Per the spec, these entries can only be accessed via
// Performance Observer. No separate buffer is maintained.
case PerformanceEntry::kLongTask:
break;
case PerformanceEntry::kTaskAttribution:
break;
// TODO(npm): decide which element timing and layout jank entries are
// accessible via the performance buffer.
case PerformanceEntry::kElement:
case PerformanceEntry::kLayoutJank:
break;
case PerformanceEntry::kInvalid:
break;
}
std::sort(entries.begin(), entries.end(),
PerformanceEntry::StartTimeCompareLessThan);
return entries;
}
PerformanceEntryVector Performance::getEntriesByName(
const AtomicString& name,
const AtomicString& entry_type) {
PerformanceEntryVector entries;
PerformanceEntry::EntryType type =
PerformanceEntry::ToEntryTypeEnum(entry_type);
if (!entry_type.IsNull() && type == PerformanceEntry::kInvalid)
return entries;
if (entry_type.IsNull() || type == PerformanceEntry::kResource) {
for (const auto& resource : resource_timing_buffer_) {
if (resource->name() == name)
entries.push_back(resource);
}
}
if (entry_type.IsNull() || type == PerformanceEntry::kEvent) {
for (const auto& event : event_timing_buffer_) {
if (event->name() == name)
entries.push_back(event);
}
}
if (type == PerformanceEntry::kEvent) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kEventTimingExplicitlyRequested);
}
if (entry_type.IsNull() || type == PerformanceEntry::kFirstInput) {
if (first_input_timing_ && first_input_timing_->name() == name)
entries.push_back(first_input_timing_);
}
if (type == PerformanceEntry::kFirstInput) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kEventTimingExplicitlyRequested);
}
if (entry_type.IsNull() || type == PerformanceEntry::kNavigation) {
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
if (navigation_timing_ && navigation_timing_->name() == name)
entries.push_back(navigation_timing_);
}
if (user_timing_) {
if (entry_type.IsNull() || type == PerformanceEntry::kMark)
entries.AppendVector(user_timing_->GetMarks(name));
if (entry_type.IsNull() || type == PerformanceEntry::kMeasure)
entries.AppendVector(user_timing_->GetMeasures(name));
}
if (entry_type.IsNull() || type == PerformanceEntry::kPaint) {
if (first_paint_timing_ && first_paint_timing_->name() == name)
entries.push_back(first_paint_timing_);
if (first_contentful_paint_timing_ &&
first_contentful_paint_timing_->name() == name)
entries.push_back(first_contentful_paint_timing_);
}
std::sort(entries.begin(), entries.end(),
PerformanceEntry::StartTimeCompareLessThan);
return entries;
}
void Performance::clearResourceTimings() {
resource_timing_buffer_.clear();
}
void Performance::setResourceTimingBufferSize(unsigned size) {
resource_timing_buffer_size_ = size;
if (IsResourceTimingBufferFull())
DispatchEvent(*Event::Create(event_type_names::kResourcetimingbufferfull));
}
bool Performance::PassesTimingAllowCheck(
const ResourceResponse& response,
const SecurityOrigin& initiator_security_origin,
const AtomicString& original_timing_allow_origin,
ExecutionContext* context) {
const KURL& response_url = response.WasFetchedViaServiceWorker()
? response.OriginalURLViaServiceWorker()
: response.Url();
scoped_refptr<const SecurityOrigin> resource_origin =
SecurityOrigin::Create(response_url);
if (resource_origin->IsSameSchemeHostPort(&initiator_security_origin))
return true;
const AtomicString& timing_allow_origin_string =
original_timing_allow_origin.IsEmpty()
? response.HttpHeaderField(http_names::kTimingAllowOrigin)
: original_timing_allow_origin;
if (timing_allow_origin_string.IsEmpty() ||
EqualIgnoringASCIICase(timing_allow_origin_string, "null"))
return false;
// The condition below if only needed for use-counting purposes.
if (timing_allow_origin_string == "*") {
UseCounter::Count(context, WebFeature::kStarInTimingAllowOrigin);
return true;
}
// TODO(yoav): Use CommaDelimitedHeaderSet instead of this one-off parsing
// algorithm.
const String& security_origin = initiator_security_origin.ToString();
Vector<String> timing_allow_origins;
timing_allow_origin_string.GetString().Split(',', timing_allow_origins);
if (timing_allow_origins.size() > 1) {
UseCounter::Count(context, WebFeature::kMultipleOriginsInTimingAllowOrigin);
} else if (timing_allow_origins.size() == 1 &&
timing_allow_origin_string != "*") {
UseCounter::Count(context, WebFeature::kSingleOriginInTimingAllowOrigin);
}
for (const String& allow_origin : timing_allow_origins) {
const String allow_origin_stripped = allow_origin.StripWhiteSpace();
if (allow_origin_stripped == security_origin ||
allow_origin_stripped == "*") {
return true;
}
}
return false;
}
bool Performance::AllowsTimingRedirect(
const Vector<ResourceResponse>& redirect_chain,
const ResourceResponse& final_response,
const SecurityOrigin& initiator_security_origin,
ExecutionContext* context) {
if (!PassesTimingAllowCheck(final_response, initiator_security_origin,
AtomicString(), context))
return false;
for (const ResourceResponse& response : redirect_chain) {
if (!PassesTimingAllowCheck(response, initiator_security_origin,
AtomicString(), context))
return false;
}
return true;
}
void Performance::GenerateAndAddResourceTiming(
const ResourceTimingInfo& info,
const AtomicString& initiator_type) {
if (IsResourceTimingBufferFull() &&
!HasObserverFor(PerformanceEntry::kResource))
return;
ExecutionContext* context = GetExecutionContext();
const SecurityOrigin* security_origin = GetSecurityOrigin(context);
if (!security_origin)
return;
AddResourceTiming(
GenerateResourceTiming(*security_origin, info, *context),
!initiator_type.IsNull() ? initiator_type : info.InitiatorType());
}
WebResourceTimingInfo Performance::GenerateResourceTiming(
const SecurityOrigin& destination_origin,
const ResourceTimingInfo& info,
ExecutionContext& context_for_use_counter) {
// TODO(dcheng): It would be nicer if the performance entries simply held this
// data internally, rather than requiring it be marshalled back and forth.
const ResourceResponse& final_response = info.FinalResponse();
WebResourceTimingInfo result;
result.name = info.InitialURL().GetString();
result.start_time = info.InitialTime();
result.alpn_negotiated_protocol = final_response.AlpnNegotiatedProtocol();
result.connection_info = final_response.ConnectionInfoString();
result.timing = final_response.GetResourceLoadTiming();
result.finish_time = info.LoadFinishTime();
result.allow_timing_details = PassesTimingAllowCheck(
final_response, destination_origin, info.OriginalTimingAllowOrigin(),
&context_for_use_counter);
const Vector<ResourceResponse>& redirect_chain = info.RedirectChain();
if (!redirect_chain.IsEmpty()) {
result.allow_redirect_details =
AllowsTimingRedirect(redirect_chain, final_response, destination_origin,
&context_for_use_counter);
// TODO(https://crbug.com/817691): is |last_chained_timing| being null a bug
// or is this if statement reasonable?
if (ResourceLoadTiming* last_chained_timing =
redirect_chain.back().GetResourceLoadTiming()) {
result.last_redirect_end_time = last_chained_timing->ReceiveHeadersEnd();
} else {
result.allow_redirect_details = false;
result.last_redirect_end_time = TimeTicks();
}
if (!result.allow_redirect_details) {
// TODO(https://crbug.com/817691): There was previously a DCHECK that
// |final_timing| is non-null. However, it clearly can be null: removing
// this check caused https://crbug.com/803811. Figure out how this can
// happen so test coverage can be added.
if (ResourceLoadTiming* final_timing =
final_response.GetResourceLoadTiming()) {
result.start_time = final_timing->RequestTime();
}
}
} else {
result.allow_redirect_details = false;
result.last_redirect_end_time = TimeTicks();
}
result.transfer_size = info.TransferSize();
result.encoded_body_size = final_response.EncodedBodyLength();
result.decoded_body_size = final_response.DecodedBodyLength();
result.did_reuse_connection = final_response.ConnectionReused();
result.allow_negative_values = info.NegativeAllowed();
if (result.allow_timing_details) {
result.server_timing = PerformanceServerTiming::ParseServerTiming(info);
}
if (!result.server_timing.empty()) {
UseCounter::Count(&context_for_use_counter,
WebFeature::kPerformanceServerTiming);
}
return result;
}
void Performance::AddResourceTiming(const WebResourceTimingInfo& info,
const AtomicString& initiator_type) {
if (IsResourceTimingBufferFull() &&
!HasObserverFor(PerformanceEntry::kResource))
return;
PerformanceEntry* entry =
PerformanceResourceTiming::Create(info, time_origin_, initiator_type);
NotifyObserversOfEntry(*entry);
if (!IsResourceTimingBufferFull())
AddResourceTimingBuffer(*entry);
}
// Called after loadEventEnd happens.
void Performance::NotifyNavigationTimingToObservers() {
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
if (navigation_timing_)
NotifyObserversOfEntry(*navigation_timing_);
}
bool Performance::IsEventTimingBufferFull() const {
return event_timing_buffer_.size() >= event_timing_buffer_max_size_;
}
void Performance::AddEventTimingBuffer(PerformanceEventTiming& entry) {
event_timing_buffer_.push_back(&entry);
if (IsEventTimingBufferFull())
DispatchEvent(*Event::Create(event_type_names::kEventtimingbufferfull));
}
unsigned Performance::EventTimingBufferSize() const {
return event_timing_buffer_.size();
}
void Performance::clearEventTimings() {
event_timing_buffer_.clear();
}
void Performance::setEventTimingBufferMaxSize(unsigned size) {
event_timing_buffer_max_size_ = size;
if (IsEventTimingBufferFull())
DispatchEvent(*Event::Create(event_type_names::kEventtimingbufferfull));
}
void Performance::AddFirstPaintTiming(TimeTicks start_time) {
AddPaintTiming(PerformancePaintTiming::PaintType::kFirstPaint, start_time);
}
void Performance::AddFirstContentfulPaintTiming(TimeTicks start_time) {
AddPaintTiming(PerformancePaintTiming::PaintType::kFirstContentfulPaint,
start_time);
}
void Performance::AddPaintTiming(PerformancePaintTiming::PaintType type,
TimeTicks start_time) {
PerformanceEntry* entry = MakeGarbageCollected<PerformancePaintTiming>(
type, MonotonicTimeToDOMHighResTimeStamp(start_time));
// Always buffer First Paint & First Contentful Paint.
if (type == PerformancePaintTiming::PaintType::kFirstPaint)
first_paint_timing_ = entry;
else if (type == PerformancePaintTiming::PaintType::kFirstContentfulPaint)
first_contentful_paint_timing_ = entry;
NotifyObserversOfEntry(*entry);
}
void Performance::AddResourceTimingBuffer(PerformanceEntry& entry) {
resource_timing_buffer_.push_back(&entry);
if (IsResourceTimingBufferFull())
DispatchEvent(*Event::Create(event_type_names::kResourcetimingbufferfull));
}
bool Performance::IsResourceTimingBufferFull() {
return resource_timing_buffer_.size() >= resource_timing_buffer_size_;
}
void Performance::AddLongTaskTiming(
TimeTicks start_time,
TimeTicks end_time,
const AtomicString& name,
const String& frame_src,
const String& frame_id,
const String& frame_name,
const SubTaskAttribution::EntriesVector& sub_task_attributions) {
if (!HasObserverFor(PerformanceEntry::kLongTask))
return;
for (auto&& it : sub_task_attributions) {
it->setHighResStartTime(
MonotonicTimeToDOMHighResTimeStamp(it->startTime()));
it->setHighResDuration(
ConvertTimeDeltaToDOMHighResTimeStamp(it->duration()));
}
PerformanceEntry* entry = PerformanceLongTaskTiming::Create(
MonotonicTimeToDOMHighResTimeStamp(start_time),
MonotonicTimeToDOMHighResTimeStamp(end_time), name, frame_src, frame_id,
frame_name, sub_task_attributions);
NotifyObserversOfEntry(*entry);
}
PerformanceMark* Performance::mark(ScriptState* script_state,
const AtomicString& mark_name,
ExceptionState& exception_state) {
DoubleOrPerformanceMarkOptions startOrOptions;
return mark(script_state, mark_name, startOrOptions, exception_state);
}
PerformanceMark* Performance::mark(
ScriptState* script_state,
const AtomicString& mark_name,
DoubleOrPerformanceMarkOptions& start_time_or_mark_options,
ExceptionState& exception_state) {
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
PerformanceMark* performance_mark = user_timing_->Mark(
script_state, mark_name, start_time_or_mark_options, exception_state);
if (performance_mark)
NotifyObserversOfEntry(*performance_mark);
if (RuntimeEnabledFeatures::CustomUserTimingEnabled())
return performance_mark;
return nullptr;
}
void Performance::clearMarks(const AtomicString& mark_name) {
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
user_timing_->ClearMarks(mark_name);
}
PerformanceMeasure* Performance::measure(ScriptState* script_state,
const AtomicString& measure_name,
ExceptionState& exception_state) {
LogMeasureStartToUma(MeasureParameterType::kUnprovided);
LogMeasureEndToUma(MeasureParameterType::kUnprovided);
return MeasureInternal(
script_state, measure_name,
NativeValueTraits<StringOrDoubleOrPerformanceMeasureOptions>::NullValue(),
NativeValueTraits<StringOrDouble>::NullValue(), exception_state);
}
PerformanceMeasure* Performance::measure(
ScriptState* script_state,
const AtomicString& measure_name,
const StringOrDoubleOrPerformanceMeasureOptions& start_or_options,
ExceptionState& exception_state) {
LogMeasureStartToUma(StartOrOptionsToParameterType(start_or_options));
LogMeasureEndToUma(MeasureParameterType::kUnprovided);
return MeasureInternal(script_state, measure_name, start_or_options,
NativeValueTraits<StringOrDouble>::NullValue(),
exception_state);
}
PerformanceMeasure* Performance::measure(
ScriptState* script_state,
const AtomicString& measure_name,
const StringOrDoubleOrPerformanceMeasureOptions& start_or_options,
const StringOrDouble& end,
ExceptionState& exception_state) {
LogMeasureStartToUma(StartOrOptionsToParameterType(start_or_options));
LogMeasureEndToUma(EndToParameterType(end));
return MeasureInternal(script_state, measure_name, start_or_options, end,
exception_state);
}
// |start_or_options|: while in options type, the value is an object {start,
// end, detail}, and |end| must be null; while in string or double type, the
// value is start. So there are two ways we can input the start and end
// (omitting |script_state| and |measure_name|):
// 1. measure(start, end)
// 2. measure({start, end})
//
// For simplicity, the method below unifies these ways into a single form -
// measure(name, start, end, detail). The mapping between two measure methods
// (using measure_ to denote the measure after tranformation) goes as follows:
// 1. measure(start, end): measure_(start, end, null)
// 2. measure({start, end, detail}, null): measure_(start, end, detail)
// 3. measure({start, end, detail}, end): invalid
//
// When |end| is null in C++, we cannot tell whether |end| is null, undefined or
// empty in JS from StringOrDouble, so we need |end_is_empty| to help
// distinguish between (null or undefined) and empty.
PerformanceMeasure* Performance::MeasureInternal(
ScriptState* script_state,
const AtomicString& measure_name,
const StringOrDoubleOrPerformanceMeasureOptions& start_or_options,
const StringOrDouble& end,
ExceptionState& exception_state) {
if (RuntimeEnabledFeatures::CustomUserTimingEnabled()) {
if (start_or_options.IsPerformanceMeasureOptions()) {
if (!end.IsNull()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"If a PerformanceMeasureOptions object was passed, |end| must be "
"null.");
return nullptr;
}
const PerformanceMeasureOptions* options =
start_or_options.GetAsPerformanceMeasureOptions();
return MeasureWithDetail(script_state, measure_name, options->startTime(),
options->endTime(), options->detail(),
exception_state);
} else {
StringOrDouble converted_start;
if (start_or_options.IsDouble()) {
converted_start =
StringOrDouble::FromDouble(start_or_options.GetAsDouble());
} else if (start_or_options.IsString()) {
converted_start =
StringOrDouble::FromString(start_or_options.GetAsString());
} else {
DCHECK(start_or_options.IsNull());
converted_start = NativeValueTraits<StringOrDouble>::NullValue();
}
// We let |end| behave the same whether it's empty, undefined or null in
// JS, as long as |end| is null in C++.
return MeasureWithDetail(script_state, measure_name, converted_start, end,
ScriptValue::CreateNull(script_state),
exception_state);
}
} else {
// For consistency with UserTimingL2: the L2 API took |start| as a string,
// so any object passed in became a string '[object, object]', null became
// string 'null'.
StringOrDouble converted_start;
if (start_or_options.IsPerformanceMeasureOptions()) {
converted_start = StringOrDouble::FromString("[object Object]");
} else if (start_or_options.IsDouble()) {
converted_start = StringOrDouble::FromString(
String::NumberToStringECMAScript(start_or_options.GetAsDouble()));
} else if (start_or_options.IsString()) {
converted_start =
StringOrDouble::FromString(start_or_options.GetAsString());
} else {
DCHECK(start_or_options.IsNull());
DCHECK(converted_start.IsNull());
}
StringOrDouble converted_end;
if (end.IsString()) {
converted_end = StringOrDouble::FromString(end.GetAsString());
} else if (end.IsDouble()) {
converted_end = StringOrDouble::FromString(
String::NumberToStringECMAScript(end.GetAsDouble()));
} else {
DCHECK(end.IsNull());
DCHECK(converted_end.IsNull());
}
MeasureWithDetail(script_state, measure_name, converted_start,
converted_end, ScriptValue::CreateNull(script_state),
exception_state);
// Return nullptr to distinguish from L3.
return nullptr;
}
}
PerformanceMeasure* Performance::MeasureWithDetail(
ScriptState* script_state,
const AtomicString& measure_name,
const StringOrDouble& start,
const StringOrDouble& end,
const ScriptValue& detail,
ExceptionState& exception_state) {
StringOrDouble original_start = start;
StringOrDouble original_end = end;
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
PerformanceMeasure* performance_measure =
user_timing_->Measure(script_state, measure_name, original_start,
original_end, detail, exception_state);
if (performance_measure)
NotifyObserversOfEntry(*performance_measure);
return performance_measure;
}
void Performance::clearMeasures(const AtomicString& measure_name) {
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
user_timing_->ClearMeasures(measure_name);
}
void Performance::RegisterPerformanceObserver(PerformanceObserver& observer) {
observer_filter_options_ |= observer.FilterOptions();
observers_.insert(&observer);
UpdateLongTaskInstrumentation();
}
void Performance::UnregisterPerformanceObserver(
PerformanceObserver& old_observer) {
observers_.erase(&old_observer);
UpdatePerformanceObserverFilterOptions();
UpdateLongTaskInstrumentation();
}
void Performance::UpdatePerformanceObserverFilterOptions() {
observer_filter_options_ = PerformanceEntry::kInvalid;
for (const auto& observer : observers_) {
observer_filter_options_ |= observer->FilterOptions();
}
UpdateLongTaskInstrumentation();
}
void Performance::NotifyObserversOfEntry(PerformanceEntry& entry) const {
bool observer_found = false;
for (auto& observer : observers_) {
if (observer->FilterOptions() & entry.EntryTypeEnum()) {
observer->EnqueuePerformanceEntry(entry);
observer_found = true;
}
}
if (observer_found && entry.EntryTypeEnum() == PerformanceEntry::kPaint)
UseCounter::Count(GetExecutionContext(), WebFeature::kPaintTimingObserved);
}
void Performance::NotifyObserversOfEntries(PerformanceEntryVector& entries) {
for (const auto& entry : entries) {
NotifyObserversOfEntry(*entry.Get());
}
}
bool Performance::HasObserverFor(
PerformanceEntry::EntryType filter_type) const {
return observer_filter_options_ & filter_type;
}
void Performance::ActivateObserver(PerformanceObserver& observer) {
if (active_observers_.IsEmpty())
deliver_observations_timer_.StartOneShot(TimeDelta(), FROM_HERE);
active_observers_.insert(&observer);
}
void Performance::ResumeSuspendedObservers() {
if (suspended_observers_.IsEmpty())
return;
PerformanceObserverVector suspended;
CopyToVector(suspended_observers_, suspended);
for (wtf_size_t i = 0; i < suspended.size(); ++i) {
if (!suspended[i]->ShouldBeSuspended()) {
suspended_observers_.erase(suspended[i]);
ActivateObserver(*suspended[i]);
}
}
}
void Performance::DeliverObservationsTimerFired(TimerBase*) {
decltype(active_observers_) observers;
active_observers_.Swap(observers);
for (const auto& observer : observers) {
if (observer->ShouldBeSuspended())
suspended_observers_.insert(observer);
else
observer->Deliver();
}
}
// static
double Performance::ClampTimeResolution(double time_seconds) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(TimeClamper, clamper, ());
return clamper.ClampTimeResolution(time_seconds);
}
// static
DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp(
TimeTicks time_origin,
TimeTicks monotonic_time,
bool allow_negative_value) {
// Avoid exposing raw platform timestamps.
if (monotonic_time.is_null() || time_origin.is_null())
return 0.0;
double clamped_time_in_seconds =
ClampTimeResolution(TimeTicksInSeconds(monotonic_time)) -
ClampTimeResolution(TimeTicksInSeconds(time_origin));
if (clamped_time_in_seconds < 0 && !allow_negative_value)
return 0.0;
return ConvertSecondsToDOMHighResTimeStamp(clamped_time_in_seconds);
}
DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp(
TimeTicks monotonic_time) const {
return MonotonicTimeToDOMHighResTimeStamp(time_origin_, monotonic_time,
false /* allow_negative_value */);
}
DOMHighResTimeStamp Performance::now() const {
return MonotonicTimeToDOMHighResTimeStamp(CurrentTimeTicks());
}
ScriptValue Performance::toJSONForBinding(ScriptState* script_state) const {
V8ObjectBuilder result(script_state);
BuildJSONValue(result);
return result.GetScriptValue();
}
void Performance::BuildJSONValue(V8ObjectBuilder& builder) const {
builder.AddNumber("timeOrigin", timeOrigin());
// |memory| is not part of the spec, omitted.
}
void Performance::Trace(blink::Visitor* visitor) {
visitor->Trace(resource_timing_buffer_);
visitor->Trace(event_timing_buffer_);
visitor->Trace(navigation_timing_);
visitor->Trace(user_timing_);
visitor->Trace(first_paint_timing_);
visitor->Trace(first_contentful_paint_timing_);
visitor->Trace(first_input_timing_);
visitor->Trace(observers_);
visitor->Trace(active_observers_);
visitor->Trace(suspended_observers_);
EventTargetWithInlineData::Trace(visitor);
}
} // namespace blink