blob: 2295bc8b693571846923acd54f19e7cdc002ee44 [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/Performance.h"
#include "bindings/core/v8/ScriptValue.h"
#include "bindings/core/v8/V8ObjectBuilder.h"
#include "core/dom/Document.h"
#include "core/dom/QualifiedName.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/loader/DocumentLoader.h"
#include "core/origin_trials/origin_trials.h"
#include "core/timing/PerformanceTiming.h"
#include "platform/loader/fetch/ResourceTimingInfo.h"
#include "platform/runtime_enabled_features.h"
#include "public/platform/TaskType.h"
static const double kLongTaskObserverThreshold = 0.05;
static const char kUnknownAttribution[] = "unknown";
static const char kAmbiguousAttribution[] = "multiple-contexts";
static const char kSameOriginAttribution[] = "same-origin";
static const char kSameOriginSelfAttribution[] = "self";
static const char kSameOriginAncestorAttribution[] = "same-origin-ancestor";
static const char kSameOriginDescendantAttribution[] = "same-origin-descendant";
static const char kCrossOriginAncestorAttribution[] = "cross-origin-ancestor";
static const char kCrossOriginDescendantAttribution[] =
"cross-origin-descendant";
static const char kCrossOriginAttribution[] = "cross-origin-unreachable";
namespace blink {
namespace {
String GetFrameAttribute(HTMLFrameOwnerElement* frame_owner,
const QualifiedName& attr_name,
bool truncate) {
String attr_value;
if (frame_owner->hasAttribute(attr_name)) {
attr_value = frame_owner->getAttribute(attr_name);
if (truncate && attr_value.length() > 100)
attr_value = attr_value.Substring(0, 100); // Truncate to 100 chars
}
return attr_value;
}
const char* SameOriginAttribution(Frame* observer_frame, Frame* culprit_frame) {
if (observer_frame == culprit_frame)
return kSameOriginSelfAttribution;
if (observer_frame->Tree().IsDescendantOf(culprit_frame))
return kSameOriginAncestorAttribution;
if (culprit_frame->Tree().IsDescendantOf(observer_frame))
return kSameOriginDescendantAttribution;
return kSameOriginAttribution;
}
bool IsSameOrigin(String key) {
return key == kSameOriginAttribution ||
key == kSameOriginDescendantAttribution ||
key == kSameOriginAncestorAttribution ||
key == kSameOriginSelfAttribution;
}
} // namespace
static double ToTimeOrigin(LocalDOMWindow* window) {
Document* document = window->document();
if (!document)
return 0.0;
DocumentLoader* loader = document->Loader();
if (!loader)
return 0.0;
return loader->GetTiming().ReferenceMonotonicTime();
}
Performance::Performance(LocalDOMWindow* window)
: PerformanceBase(
ToTimeOrigin(window),
window->document()->GetTaskRunner(TaskType::kPerformanceTimeline)),
DOMWindowClient(window) {}
Performance::~Performance() = default;
ExecutionContext* Performance::GetExecutionContext() const {
if (!GetFrame())
return nullptr;
return GetFrame()->GetDocument();
}
MemoryInfo* Performance::memory() {
return MemoryInfo::Create();
}
PerformanceNavigation* Performance::navigation() const {
if (!navigation_)
navigation_ = PerformanceNavigation::Create(GetFrame());
return navigation_.Get();
}
PerformanceTiming* Performance::timing() const {
if (!timing_)
timing_ = PerformanceTiming::Create(GetFrame());
return timing_.Get();
}
PerformanceNavigationTiming* Performance::CreateNavigationTimingInstance() {
if (!RuntimeEnabledFeatures::PerformanceNavigationTiming2Enabled())
return nullptr;
if (!GetFrame())
return nullptr;
const DocumentLoader* document_loader =
GetFrame()->Loader().GetDocumentLoader();
DCHECK(document_loader);
ResourceTimingInfo* info = document_loader->GetNavigationTimingInfo();
if (!info)
return nullptr;
WebVector<WebServerTimingInfo> server_timing =
PerformanceServerTiming::ParseServerTiming(
*info, PerformanceServerTiming::ShouldAllowTimingDetails::Yes);
if (!server_timing.empty())
UseCounter::Count(GetFrame(), WebFeature::kPerformanceServerTiming);
return new PerformanceNavigationTiming(GetFrame(), info, GetTimeOrigin(),
server_timing);
}
void Performance::UpdateLongTaskInstrumentation() {
if (!GetFrame() || !GetFrame()->GetDocument())
return;
if (HasObserverFor(PerformanceEntry::kLongTask)) {
UseCounter::Count(&GetFrame()->LocalFrameRoot(),
WebFeature::kLongTaskObserver);
GetFrame()->GetPerformanceMonitor()->Subscribe(
PerformanceMonitor::kLongTask, kLongTaskObserverThreshold, this);
} else {
GetFrame()->GetPerformanceMonitor()->UnsubscribeAll(this);
}
}
void Performance::BuildJSONValue(V8ObjectBuilder& builder) const {
PerformanceBase::BuildJSONValue(builder);
builder.Add("timing", timing()->toJSONForBinding(builder.GetScriptState()));
builder.Add("navigation",
navigation()->toJSONForBinding(builder.GetScriptState()));
}
void Performance::Trace(blink::Visitor* visitor) {
visitor->Trace(navigation_);
visitor->Trace(timing_);
PerformanceBase::Trace(visitor);
PerformanceMonitor::Client::Trace(visitor);
DOMWindowClient::Trace(visitor);
}
static bool CanAccessOrigin(Frame* frame1, Frame* frame2) {
const SecurityOrigin* security_origin1 =
frame1->GetSecurityContext()->GetSecurityOrigin();
const SecurityOrigin* security_origin2 =
frame2->GetSecurityContext()->GetSecurityOrigin();
return security_origin1->CanAccess(security_origin2);
}
/**
* Report sanitized name based on cross-origin policy.
* See detailed Security doc here: http://bit.ly/2duD3F7
*/
// static
std::pair<String, DOMWindow*> Performance::SanitizedAttribution(
ExecutionContext* task_context,
bool has_multiple_contexts,
LocalFrame* observer_frame) {
if (has_multiple_contexts) {
// Unable to attribute, multiple script execution contents were involved.
return std::make_pair(kAmbiguousAttribution, nullptr);
}
if (!task_context || !task_context->IsDocument() ||
!ToDocument(task_context)->GetFrame()) {
// Unable to attribute as no script was involved.
return std::make_pair(kUnknownAttribution, nullptr);
}
// Exactly one culprit location, attribute based on origin boundary.
Frame* culprit_frame = ToDocument(task_context)->GetFrame();
DCHECK(culprit_frame);
if (CanAccessOrigin(observer_frame, culprit_frame)) {
// From accessible frames or same origin, return culprit location URL.
return std::make_pair(SameOriginAttribution(observer_frame, culprit_frame),
culprit_frame->DomWindow());
}
// For cross-origin, if the culprit is the descendant or ancestor of
// observer then indicate the *closest* cross-origin frame between
// the observer and the culprit, in the corresponding direction.
if (culprit_frame->Tree().IsDescendantOf(observer_frame)) {
// If the culprit is a descendant of the observer, then walk up the tree
// from culprit to observer, and report the *last* cross-origin (from
// observer) frame. If no intermediate cross-origin frame is found, then
// report the culprit directly.
Frame* last_cross_origin_frame = culprit_frame;
for (Frame* frame = culprit_frame; frame != observer_frame;
frame = frame->Tree().Parent()) {
if (!CanAccessOrigin(observer_frame, frame)) {
last_cross_origin_frame = frame;
}
}
return std::make_pair(kCrossOriginDescendantAttribution,
last_cross_origin_frame->DomWindow());
}
if (observer_frame->Tree().IsDescendantOf(culprit_frame)) {
return std::make_pair(kCrossOriginAncestorAttribution, nullptr);
}
return std::make_pair(kCrossOriginAttribution, nullptr);
}
void Performance::ReportLongTask(
double start_time,
double end_time,
ExecutionContext* task_context,
bool has_multiple_contexts,
const SubTaskAttribution::EntriesVector& sub_task_attributions) {
if (!GetFrame())
return;
std::pair<String, DOMWindow*> attribution = Performance::SanitizedAttribution(
task_context, has_multiple_contexts, GetFrame());
DOMWindow* culprit_dom_window = attribution.second;
SubTaskAttribution::EntriesVector empty_vector;
if (!culprit_dom_window || !culprit_dom_window->GetFrame() ||
!culprit_dom_window->GetFrame()->DeprecatedLocalOwner()) {
AddLongTaskTiming(
start_time, end_time, attribution.first, g_empty_string, g_empty_string,
g_empty_string,
IsSameOrigin(attribution.first) ? sub_task_attributions : empty_vector);
} else {
HTMLFrameOwnerElement* frame_owner =
culprit_dom_window->GetFrame()->DeprecatedLocalOwner();
AddLongTaskTiming(
start_time, end_time, attribution.first,
GetFrameAttribute(frame_owner, HTMLNames::srcAttr, false),
GetFrameAttribute(frame_owner, HTMLNames::idAttr, false),
GetFrameAttribute(frame_owner, HTMLNames::nameAttr, true),
IsSameOrigin(attribution.first) ? sub_task_attributions : empty_vector);
}
}
} // namespace blink