blob: 15820dc4a67d618eee1aa6ec1f2a1117a4f66265 [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 "core/timing/PerformanceBase.h"
#include <algorithm>
#include "bindings/core/v8/V8ObjectBuilder.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentTiming.h"
#include "core/dom/events/Event.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/loader/DocumentLoadTiming.h"
#include "core/loader/DocumentLoader.h"
#include "core/timing/PerformanceLongTaskTiming.h"
#include "core/timing/PerformanceObserver.h"
#include "core/timing/PerformanceResourceTiming.h"
#include "core/timing/PerformanceUserTiming.h"
#include "platform/Histogram.h"
#include "platform/loader/fetch/ResourceResponse.h"
#include "platform/loader/fetch/ResourceTimingInfo.h"
#include "platform/runtime_enabled_features.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/wtf/StdLibExtras.h"
#include "platform/wtf/Time.h"
namespace blink {
namespace {
const SecurityOrigin* GetSecurityOrigin(ExecutionContext* context) {
if (context)
return context->GetSecurityOrigin();
return nullptr;
}
// When a PerformanceBase 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() -
MonotonicallyIncreasingTime())});
return unix_at_zero_monotonic;
}
bool IsNavigationTimingType(
PerformanceBase::PerformanceMeasurePassedInParameterType type) {
return type != PerformanceBase::kObjectObject &&
type != PerformanceBase::kOther;
}
} // namespace
using PerformanceObserverVector = HeapVector<Member<PerformanceObserver>>;
static const size_t kDefaultResourceTimingBufferSize = 150;
static const size_t kDefaultFrameTimingBufferSize = 150;
PerformanceBase::PerformanceBase(double time_origin,
scoped_refptr<WebTaskRunner> task_runner)
: frame_timing_buffer_size_(kDefaultFrameTimingBufferSize),
resource_timing_buffer_size_(kDefaultResourceTimingBufferSize),
user_timing_(nullptr),
time_origin_(time_origin),
observer_filter_options_(PerformanceEntry::kInvalid),
deliver_observations_timer_(
std::move(task_runner),
this,
&PerformanceBase::DeliverObservationsTimerFired) {}
PerformanceBase::~PerformanceBase() {}
const AtomicString& PerformanceBase::InterfaceName() const {
return EventTargetNames::Performance;
}
PerformanceTiming* PerformanceBase::timing() const {
return nullptr;
}
DOMHighResTimeStamp PerformanceBase::timeOrigin() const {
DCHECK(time_origin_ > 0.0);
return GetUnixAtZeroMonotonic() +
ConvertSecondsToDOMHighResTimeStamp(time_origin_);
}
PerformanceEntryVector PerformanceBase::getEntries() {
PerformanceEntryVector entries;
entries.AppendVector(resource_timing_buffer_);
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
// This extra checking is needed when WorkerPerformance
// calls this method.
if (navigation_timing_)
entries.push_back(navigation_timing_);
entries.AppendVector(frame_timing_buffer_);
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 PerformanceBase::getEntriesByType(
const String& 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::kNavigation:
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
if (navigation_timing_)
entries.push_back(navigation_timing_);
break;
case PerformanceEntry::kComposite:
case PerformanceEntry::kRender:
for (const auto& frame : frame_timing_buffer_) {
if (type == frame->EntryTypeEnum()) {
entries.push_back(frame);
}
}
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;
case PerformanceEntry::kInvalid:
break;
}
std::sort(entries.begin(), entries.end(),
PerformanceEntry::StartTimeCompareLessThan);
return entries;
}
PerformanceEntryVector PerformanceBase::getEntriesByName(
const String& name,
const String& 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::kNavigation) {
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
if (navigation_timing_ && navigation_timing_->name() == name)
entries.push_back(navigation_timing_);
}
if (entry_type.IsNull() || type == PerformanceEntry::kComposite ||
type == PerformanceEntry::kRender) {
for (const auto& frame : frame_timing_buffer_) {
if (frame->name() == name &&
(entry_type.IsNull() || entry_type == frame->entryType()))
entries.push_back(frame);
}
}
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 PerformanceBase::clearResourceTimings() {
resource_timing_buffer_.clear();
}
void PerformanceBase::setResourceTimingBufferSize(unsigned size) {
resource_timing_buffer_size_ = size;
if (IsResourceTimingBufferFull())
DispatchEvent(Event::Create(EventTypeNames::resourcetimingbufferfull));
}
bool PerformanceBase::PassesTimingAllowCheck(
const ResourceResponse& response,
const SecurityOrigin& initiator_security_origin,
const AtomicString& original_timing_allow_origin,
ExecutionContext* context) {
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(HTTPNames::Timing_Allow_Origin)
: original_timing_allow_origin;
if (timing_allow_origin_string.IsEmpty() ||
EqualIgnoringASCIICase(timing_allow_origin_string, "null"))
return false;
if (timing_allow_origin_string == "*") {
UseCounter::Count(context, WebFeature::kStarInTimingAllowOrigin);
return true;
}
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)
UseCounter::Count(context, WebFeature::kSingleOriginInTimingAllowOrigin);
for (const String& allow_origin : timing_allow_origins) {
if (allow_origin == security_origin)
return true;
}
return false;
}
bool PerformanceBase::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 PerformanceBase::AddResourceTiming(const ResourceTimingInfo& info) {
if (IsResourceTimingBufferFull() &&
!HasObserverFor(PerformanceEntry::kResource))
return;
ExecutionContext* context = GetExecutionContext();
const SecurityOrigin* security_origin = GetSecurityOrigin(context);
if (!security_origin)
return;
const ResourceResponse& final_response = info.FinalResponse();
bool allow_timing_details =
PassesTimingAllowCheck(final_response, *security_origin,
info.OriginalTimingAllowOrigin(), context);
double start_time = info.InitialTime();
PerformanceServerTimingVector serverTiming =
PerformanceServerTiming::ParseServerTiming(
info, allow_timing_details
? PerformanceServerTiming::ShouldAllowTimingDetails::Yes
: PerformanceServerTiming::ShouldAllowTimingDetails::No);
if (serverTiming.size()) {
UseCounter::Count(context, WebFeature::kPerformanceServerTiming);
}
if (info.RedirectChain().IsEmpty()) {
PerformanceEntry* entry = PerformanceResourceTiming::Create(
info, GetTimeOrigin(), start_time, allow_timing_details, serverTiming);
NotifyObserversOfEntry(*entry);
if (!IsResourceTimingBufferFull())
AddResourceTimingBuffer(*entry);
return;
}
const Vector<ResourceResponse>& redirect_chain = info.RedirectChain();
bool allow_redirect_details = AllowsTimingRedirect(
redirect_chain, final_response, *security_origin, context);
if (!allow_redirect_details) {
ResourceLoadTiming* final_timing = final_response.GetResourceLoadTiming();
DCHECK(final_timing);
if (final_timing)
start_time = final_timing->RequestTime();
}
ResourceLoadTiming* last_redirect_timing =
redirect_chain.back().GetResourceLoadTiming();
CHECK(last_redirect_timing);
double last_redirect_end_time = last_redirect_timing->ReceiveHeadersEnd();
PerformanceEntry* entry = PerformanceResourceTiming::Create(
info, GetTimeOrigin(), start_time, last_redirect_end_time,
allow_timing_details, allow_redirect_details, serverTiming);
NotifyObserversOfEntry(*entry);
if (!IsResourceTimingBufferFull())
AddResourceTimingBuffer(*entry);
}
// Called after loadEventEnd happens.
void PerformanceBase::NotifyNavigationTimingToObservers() {
if (!navigation_timing_)
navigation_timing_ = CreateNavigationTimingInstance();
if (navigation_timing_)
NotifyObserversOfEntry(*navigation_timing_);
}
void PerformanceBase::AddFirstPaintTiming(double start_time) {
AddPaintTiming(PerformancePaintTiming::PaintType::kFirstPaint, start_time);
}
void PerformanceBase::AddFirstContentfulPaintTiming(double start_time) {
AddPaintTiming(PerformancePaintTiming::PaintType::kFirstContentfulPaint,
start_time);
}
void PerformanceBase::AddPaintTiming(PerformancePaintTiming::PaintType type,
double start_time) {
if (!RuntimeEnabledFeatures::PerformancePaintTimingEnabled())
return;
PerformanceEntry* entry = new 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 PerformanceBase::AddResourceTimingBuffer(PerformanceEntry& entry) {
resource_timing_buffer_.push_back(&entry);
if (IsResourceTimingBufferFull())
DispatchEvent(Event::Create(EventTypeNames::resourcetimingbufferfull));
}
bool PerformanceBase::IsResourceTimingBufferFull() {
return resource_timing_buffer_.size() >= resource_timing_buffer_size_;
}
void PerformanceBase::AddLongTaskTiming(
double start_time,
double end_time,
const String& 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(ConvertSecondsToDOMHighResTimeStamp(it->duration()));
}
PerformanceEntry* entry = PerformanceLongTaskTiming::Create(
MonotonicTimeToDOMHighResTimeStamp(start_time),
MonotonicTimeToDOMHighResTimeStamp(end_time), name, frame_src, frame_id,
frame_name, sub_task_attributions);
NotifyObserversOfEntry(*entry);
}
void PerformanceBase::mark(const String& mark_name,
ExceptionState& exception_state) {
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
if (PerformanceEntry* entry = user_timing_->Mark(mark_name, exception_state))
NotifyObserversOfEntry(*entry);
}
void PerformanceBase::clearMarks(const String& mark_name) {
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
user_timing_->ClearMarks(mark_name);
}
void PerformanceBase::measure(const String& measure_name,
const String& start_mark,
const String& end_mark,
ExceptionState& exception_state) {
UMA_HISTOGRAM_ENUMERATION(
"Performance.PerformanceMeasurePassedInParameter.StartMark",
ToPerformanceMeasurePassedInParameterType(start_mark),
kPerformanceMeasurePassedInParameterCount);
UMA_HISTOGRAM_ENUMERATION(
"Performance.PerformanceMeasurePassedInParameter.EndMark",
ToPerformanceMeasurePassedInParameterType(end_mark),
kPerformanceMeasurePassedInParameterCount);
ExecutionContext* execution_context = GetExecutionContext();
if (execution_context) {
PerformanceMeasurePassedInParameterType start_type =
ToPerformanceMeasurePassedInParameterType(start_mark);
PerformanceMeasurePassedInParameterType end_type =
ToPerformanceMeasurePassedInParameterType(end_mark);
if (start_type == kObjectObject) {
UseCounter::Count(execution_context,
WebFeature::kPerformanceMeasurePassedInObject);
}
if (IsNavigationTimingType(start_type) ||
IsNavigationTimingType(end_type)) {
UseCounter::Count(
execution_context,
WebFeature::kPerformanceMeasurePassedInNavigationTiming);
}
}
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
if (PerformanceEntry* entry = user_timing_->Measure(
measure_name, start_mark, end_mark, exception_state))
NotifyObserversOfEntry(*entry);
}
void PerformanceBase::clearMeasures(const String& measure_name) {
if (!user_timing_)
user_timing_ = UserTiming::Create(*this);
user_timing_->ClearMeasures(measure_name);
}
void PerformanceBase::RegisterPerformanceObserver(
PerformanceObserver& observer) {
observer_filter_options_ |= observer.FilterOptions();
observers_.insert(&observer);
UpdateLongTaskInstrumentation();
}
void PerformanceBase::UnregisterPerformanceObserver(
PerformanceObserver& old_observer) {
observers_.erase(&old_observer);
UpdatePerformanceObserverFilterOptions();
UpdateLongTaskInstrumentation();
}
void PerformanceBase::UpdatePerformanceObserverFilterOptions() {
observer_filter_options_ = PerformanceEntry::kInvalid;
for (const auto& observer : observers_) {
observer_filter_options_ |= observer->FilterOptions();
}
UpdateLongTaskInstrumentation();
}
void PerformanceBase::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 PerformanceBase::NotifyObserversOfEntries(
PerformanceEntryVector& entries) {
for (const auto& entry : entries) {
NotifyObserversOfEntry(*entry.Get());
}
}
bool PerformanceBase::HasObserverFor(
PerformanceEntry::EntryType filter_type) const {
return observer_filter_options_ & filter_type;
}
void PerformanceBase::ActivateObserver(PerformanceObserver& observer) {
if (active_observers_.IsEmpty())
deliver_observations_timer_.StartOneShot(TimeDelta(), BLINK_FROM_HERE);
active_observers_.insert(&observer);
}
void PerformanceBase::ResumeSuspendedObservers() {
if (suspended_observers_.IsEmpty())
return;
PerformanceObserverVector suspended;
CopyToVector(suspended_observers_, suspended);
for (size_t i = 0; i < suspended.size(); ++i) {
if (!suspended[i]->ShouldBeSuspended()) {
suspended_observers_.erase(suspended[i]);
ActivateObserver(*suspended[i]);
}
}
}
void PerformanceBase::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 PerformanceBase::ClampTimeResolution(double time_seconds) {
const double kResolutionSeconds = 0.000005;
return floor(time_seconds / kResolutionSeconds) * kResolutionSeconds;
}
// static
DOMHighResTimeStamp PerformanceBase::MonotonicTimeToDOMHighResTimeStamp(
double time_origin,
double monotonic_time,
bool allow_negative_value) {
// Avoid exposing raw platform timestamps.
if (!monotonic_time || !time_origin)
return 0.0;
double time_in_seconds = monotonic_time - time_origin;
if (time_in_seconds < 0 && !allow_negative_value)
return 0.0;
return ConvertSecondsToDOMHighResTimeStamp(
ClampTimeResolution(time_in_seconds));
}
DOMHighResTimeStamp PerformanceBase::MonotonicTimeToDOMHighResTimeStamp(
double monotonic_time) const {
return MonotonicTimeToDOMHighResTimeStamp(time_origin_, monotonic_time,
false /* allow_negative_value */);
}
DOMHighResTimeStamp PerformanceBase::now() const {
return MonotonicTimeToDOMHighResTimeStamp(MonotonicallyIncreasingTime());
}
ScriptValue PerformanceBase::toJSONForBinding(ScriptState* script_state) const {
V8ObjectBuilder result(script_state);
BuildJSONValue(result);
return result.GetScriptValue();
}
void PerformanceBase::BuildJSONValue(V8ObjectBuilder& builder) const {
builder.AddNumber("timeOrigin", timeOrigin());
// |memory| is not part of the spec, omitted.
}
void PerformanceBase::Trace(blink::Visitor* visitor) {
visitor->Trace(frame_timing_buffer_);
visitor->Trace(resource_timing_buffer_);
visitor->Trace(navigation_timing_);
visitor->Trace(user_timing_);
visitor->Trace(first_paint_timing_);
visitor->Trace(first_contentful_paint_timing_);
visitor->Trace(observers_);
visitor->Trace(active_observers_);
visitor->Trace(suspended_observers_);
EventTargetWithInlineData::Trace(visitor);
}
void PerformanceBase::TraceWrappers(
const ScriptWrappableVisitor* visitor) const {
for (const auto& observer : observers_)
visitor->TraceWrappers(observer);
EventTargetWithInlineData::TraceWrappers(visitor);
}
} // namespace blink