blob: 5d539b6f7449780b8807a56677df6cec76528d05 [file] [log] [blame]
// Copyright 2016 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 "core/inspector/InspectorWebPerfAgent.h"
#include "core/InstrumentingAgents.h"
#include "core/dom/Document.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/DOMWindow.h"
#include "core/frame/Frame.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Location.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/InspectedFrames.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "public/platform/Platform.h"
namespace blink {
namespace {
static const double kLongTaskThresholdMillis = 50.0;
static const char kUnknownAttribution[] = "unknown";
static const char kAmbugiousAttribution[] = "multiple-contexts";
static const char kSameOriginAttribution[] = "same-origin";
static const char kAncestorAttribution[] = "cross-origin-ancestor";
static const char kDescendantAttribution[] = "cross-origin-descendant";
static const char kCrossOriginAttribution[] = "cross-origin-unreachable";
bool canAccessOrigin(Frame* frame1, Frame* frame2) {
SecurityOrigin* securityOrigin1 =
frame1->securityContext()->getSecurityOrigin();
SecurityOrigin* securityOrigin2 =
frame2->securityContext()->getSecurityOrigin();
// TODO(panicker): Confirm this pending security review.
return securityOrigin1->canAccess(securityOrigin2);
}
} // namespace
InspectorWebPerfAgent::InspectorWebPerfAgent(LocalFrame* localFrame)
: m_localFrame(localFrame) {}
InspectorWebPerfAgent::~InspectorWebPerfAgent() {
DCHECK(!m_enabled);
}
void InspectorWebPerfAgent::enable() {
// Update usage count.
UseCounter::count(m_localFrame, UseCounter::LongTaskObserver);
Platform::current()->currentThread()->addTaskTimeObserver(this);
Platform::current()->currentThread()->addTaskObserver(this);
m_localFrame->instrumentingAgents()->addInspectorWebPerfAgent(this);
m_enabled = true;
}
void InspectorWebPerfAgent::disable() {
Platform::current()->currentThread()->removeTaskTimeObserver(this);
Platform::current()->currentThread()->removeTaskObserver(this);
m_localFrame->instrumentingAgents()->removeInspectorWebPerfAgent(this);
m_enabled = false;
}
void InspectorWebPerfAgent::willExecuteScript(ExecutionContext* context) {
// Heuristic for minimal frame context attribution: note the Location URL
// for each script execution. When a long task is encountered,
// if there is only one Location URL involved, then report it.
// Otherwise don't report Location URL.
// NOTE: This heuristic is imperfect and will be improved in V2 API.
// In V2, timing of script execution along with style & layout updates will be
// accounted for detailed and more accurate attribution.
if (context->isDocument())
m_frameContextLocations.add(toDocument(context)->location());
}
void InspectorWebPerfAgent::didExecuteScript() {}
void InspectorWebPerfAgent::willProcessTask() {
// Reset m_frameContextLocations. We don't clear this in didProcessTask
// as it is needed in ReportTaskTime which occurs after didProcessTask.
m_frameContextLocations.clear();
}
void InspectorWebPerfAgent::didProcessTask() {}
void InspectorWebPerfAgent::ReportTaskTime(scheduler::TaskQueue*,
double startTime,
double endTime) {
if (((endTime - startTime) * 1000) <= kLongTaskThresholdMillis)
return;
DOMWindow* domWindow = m_localFrame->domWindow();
if (!domWindow)
return;
Performance* performance = DOMWindowPerformance::performance(*domWindow);
DCHECK(performance);
std::pair<String, DOMWindow*> attribution =
sanitizedAttribution(m_frameContextLocations, m_localFrame);
performance->addLongTaskTiming(startTime, endTime, attribution.first,
attribution.second);
}
/**
* Report sanitized name based on cross-origin policy.
* See detailed Security doc here: http://bit.ly/2duD3F7
*/
std::pair<String, DOMWindow*> InspectorWebPerfAgent::sanitizedAttribution(
const HeapHashSet<Member<Location>>& frameContextLocations,
Frame* observerFrame) {
if (frameContextLocations.size() == 0) {
// Unable to attribute as no script was involved.
return std::make_pair(kUnknownAttribution, nullptr);
}
if (frameContextLocations.size() > 1) {
// Unable to attribute, multiple script execution contents were involved.
return std::make_pair(kAmbugiousAttribution, nullptr);
}
// Exactly one culprit location, attribute based on origin boundary.
DCHECK_EQ(1u, frameContextLocations.size());
Location* culpritLocation = *frameContextLocations.begin();
if (canAccessOrigin(observerFrame, culpritLocation->frame())) {
// From accessible frames or same origin, return culprit location URL.
return std::make_pair(kSameOriginAttribution,
culpritLocation->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 (culpritLocation->frame()->tree().isDescendantOf(observerFrame)) {
// 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* lastCrossOriginFrame = culpritLocation->frame();
for (Frame* frame = culpritLocation->frame(); frame != observerFrame;
frame = frame->tree().parent()) {
if (!canAccessOrigin(observerFrame, frame)) {
lastCrossOriginFrame = frame;
}
}
return std::make_pair(kDescendantAttribution,
lastCrossOriginFrame->domWindow());
}
if (observerFrame->tree().isDescendantOf(culpritLocation->frame())) {
return std::make_pair(kAncestorAttribution, nullptr);
}
return std::make_pair(kCrossOriginAttribution, nullptr);
}
DEFINE_TRACE(InspectorWebPerfAgent) {
visitor->trace(m_localFrame);
visitor->trace(m_frameContextLocations);
}
} // namespace blink