Commit 618e62b5 authored by Yoav Weiss's avatar Yoav Weiss Committed by Commit Bot

Align resource timing buffer full processing to spec PR 168 (take 3)

This change implements the processing model from PR 168[1], when
it comes to setResourceTimingBufferSize(), clearResourceTimings()
and the firing of the resourcetimingbufferfull event.

This is a reland of [2] which is a reland of [3] (but with nicer tests).

[1] https://github.com/w3c/resource-timing/pull/168
[2] https://chromium-review.googlesource.com/c/chromium/src/+/1350950
[3] https://chromium-review.googlesource.com/c/chromium/src/+/1345269

Bug: 908181, 908414
Change-Id: I3a6c6e9d6a9aa5b5f907d1e86bec701ff2fa022d
Reviewed-on: https://chromium-review.googlesource.com/c/1373819Reviewed-by: default avatarNicolás Peña Moreno <npm@chromium.org>
Commit-Queue: Yoav Weiss <yoavweiss@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615929}
parent ad3c758a
...@@ -177,15 +177,19 @@ constexpr size_t kDefaultEventTimingBufferSize = 150; ...@@ -177,15 +177,19 @@ constexpr size_t kDefaultEventTimingBufferSize = 150;
Performance::Performance( Performance::Performance(
TimeTicks time_origin, TimeTicks time_origin,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: resource_timing_buffer_size_(kDefaultResourceTimingBufferSize), : resource_timing_buffer_size_limit_(kDefaultResourceTimingBufferSize),
event_timing_buffer_max_size_(kDefaultEventTimingBufferSize), event_timing_buffer_max_size_(kDefaultEventTimingBufferSize),
user_timing_(nullptr), user_timing_(nullptr),
time_origin_(time_origin), time_origin_(time_origin),
observer_filter_options_(PerformanceEntry::kInvalid), observer_filter_options_(PerformanceEntry::kInvalid),
deliver_observations_timer_(std::move(task_runner), task_runner_(std::move(task_runner)),
deliver_observations_timer_(task_runner_,
this, this,
&Performance::DeliverObservationsTimerFired) { &Performance::DeliverObservationsTimerFired),
} resource_timing_buffer_full_timer_(
task_runner_,
this,
&Performance::FireResourceTimingBufferFull) {}
Performance::~Performance() = default; Performance::~Performance() = default;
...@@ -379,9 +383,7 @@ void Performance::clearResourceTimings() { ...@@ -379,9 +383,7 @@ void Performance::clearResourceTimings() {
} }
void Performance::setResourceTimingBufferSize(unsigned size) { void Performance::setResourceTimingBufferSize(unsigned size) {
resource_timing_buffer_size_ = size; resource_timing_buffer_size_limit_ = size;
if (IsResourceTimingBufferFull())
DispatchEvent(*Event::Create(event_type_names::kResourcetimingbufferfull));
} }
bool Performance::PassesTimingAllowCheck( bool Performance::PassesTimingAllowCheck(
...@@ -452,10 +454,6 @@ bool Performance::AllowsTimingRedirect( ...@@ -452,10 +454,6 @@ bool Performance::AllowsTimingRedirect(
void Performance::GenerateAndAddResourceTiming( void Performance::GenerateAndAddResourceTiming(
const ResourceTimingInfo& info, const ResourceTimingInfo& info,
const AtomicString& initiator_type) { const AtomicString& initiator_type) {
if (IsResourceTimingBufferFull() &&
!HasObserverFor(PerformanceEntry::kResource))
return;
ExecutionContext* context = GetExecutionContext(); ExecutionContext* context = GetExecutionContext();
const SecurityOrigin* security_origin = GetSecurityOrigin(context); const SecurityOrigin* security_origin = GetSecurityOrigin(context);
if (!security_origin) if (!security_origin)
...@@ -533,15 +531,20 @@ WebResourceTimingInfo Performance::GenerateResourceTiming( ...@@ -533,15 +531,20 @@ WebResourceTimingInfo Performance::GenerateResourceTiming(
void Performance::AddResourceTiming(const WebResourceTimingInfo& info, void Performance::AddResourceTiming(const WebResourceTimingInfo& info,
const AtomicString& initiator_type) { const AtomicString& initiator_type) {
if (IsResourceTimingBufferFull() &&
!HasObserverFor(PerformanceEntry::kResource))
return;
PerformanceEntry* entry = PerformanceEntry* entry =
PerformanceResourceTiming::Create(info, time_origin_, initiator_type); PerformanceResourceTiming::Create(info, time_origin_, initiator_type);
NotifyObserversOfEntry(*entry); NotifyObserversOfEntry(*entry);
if (!IsResourceTimingBufferFull()) // https://w3c.github.io/resource-timing/#dfn-add-a-performanceresourcetiming-entry
AddResourceTimingBuffer(*entry); if (CanAddResourceTimingEntry() &&
!resource_timing_buffer_full_event_pending_) {
resource_timing_buffer_.push_back(entry);
return;
}
if (!resource_timing_buffer_full_event_pending_) {
resource_timing_buffer_full_event_pending_ = true;
resource_timing_buffer_full_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
resource_timing_secondary_buffer_.push_back(entry);
} }
// Called after loadEventEnd happens. // Called after loadEventEnd happens.
...@@ -556,6 +559,35 @@ bool Performance::IsEventTimingBufferFull() const { ...@@ -556,6 +559,35 @@ bool Performance::IsEventTimingBufferFull() const {
return event_timing_buffer_.size() >= event_timing_buffer_max_size_; return event_timing_buffer_.size() >= event_timing_buffer_max_size_;
} }
void Performance::CopySecondaryBuffer() {
// https://w3c.github.io/resource-timing/#dfn-copy-secondary-buffer
while (resource_timing_secondary_buffer_.size() &&
CanAddResourceTimingEntry()) {
PerformanceEntry* entry = resource_timing_secondary_buffer_.front();
DCHECK(entry);
resource_timing_secondary_buffer_.pop_front();
resource_timing_buffer_.push_back(entry);
}
}
void Performance::FireResourceTimingBufferFull(TimerBase*) {
// https://w3c.github.io/resource-timing/#dfn-fire-a-buffer-full-event
while (resource_timing_secondary_buffer_.size()) {
int excess_entries_before = resource_timing_secondary_buffer_.size();
if (!CanAddResourceTimingEntry()) {
DispatchEvent(
*Event::Create(event_type_names::kResourcetimingbufferfull));
}
CopySecondaryBuffer();
int excess_entries_after = resource_timing_secondary_buffer_.size();
if (excess_entries_after >= excess_entries_before) {
resource_timing_secondary_buffer_.clear();
break;
}
}
resource_timing_buffer_full_event_pending_ = false;
}
void Performance::AddEventTimingBuffer(PerformanceEventTiming& entry) { void Performance::AddEventTimingBuffer(PerformanceEventTiming& entry) {
event_timing_buffer_.push_back(&entry); event_timing_buffer_.push_back(&entry);
...@@ -571,6 +603,7 @@ void Performance::clearEventTimings() { ...@@ -571,6 +603,7 @@ void Performance::clearEventTimings() {
event_timing_buffer_.clear(); event_timing_buffer_.clear();
} }
// TODO(yoav): EventTiming should follow a simpler buffering model.
void Performance::setEventTimingBufferMaxSize(unsigned size) { void Performance::setEventTimingBufferMaxSize(unsigned size) {
event_timing_buffer_max_size_ = size; event_timing_buffer_max_size_ = size;
if (IsEventTimingBufferFull()) if (IsEventTimingBufferFull())
...@@ -598,15 +631,9 @@ void Performance::AddPaintTiming(PerformancePaintTiming::PaintType type, ...@@ -598,15 +631,9 @@ void Performance::AddPaintTiming(PerformancePaintTiming::PaintType type,
NotifyObserversOfEntry(*entry); NotifyObserversOfEntry(*entry);
} }
void Performance::AddResourceTimingBuffer(PerformanceEntry& entry) { bool Performance::CanAddResourceTimingEntry() {
resource_timing_buffer_.push_back(&entry); // https://w3c.github.io/resource-timing/#dfn-can-add-resource-timing-entry
return resource_timing_buffer_.size() < resource_timing_buffer_size_limit_;
if (IsResourceTimingBufferFull())
DispatchEvent(*Event::Create(event_type_names::kResourcetimingbufferfull));
}
bool Performance::IsResourceTimingBufferFull() {
return resource_timing_buffer_.size() >= resource_timing_buffer_size_;
} }
void Performance::AddLongTaskTiming( void Performance::AddLongTaskTiming(
...@@ -936,6 +963,7 @@ void Performance::BuildJSONValue(V8ObjectBuilder& builder) const { ...@@ -936,6 +963,7 @@ void Performance::BuildJSONValue(V8ObjectBuilder& builder) const {
void Performance::Trace(blink::Visitor* visitor) { void Performance::Trace(blink::Visitor* visitor) {
visitor->Trace(resource_timing_buffer_); visitor->Trace(resource_timing_buffer_);
visitor->Trace(resource_timing_secondary_buffer_);
visitor->Trace(event_timing_buffer_); visitor->Trace(event_timing_buffer_);
visitor->Trace(navigation_timing_); visitor->Trace(navigation_timing_);
visitor->Trace(user_timing_); visitor->Trace(user_timing_);
......
...@@ -73,6 +73,7 @@ class PerformanceEventTiming; ...@@ -73,6 +73,7 @@ class PerformanceEventTiming;
class StringOrDoubleOrPerformanceMeasureOptions; class StringOrDoubleOrPerformanceMeasureOptions;
using PerformanceEntryVector = HeapVector<Member<PerformanceEntry>>; using PerformanceEntryVector = HeapVector<Member<PerformanceEntry>>;
using PerformanceEntryDeque = HeapDeque<Member<PerformanceEntry>>;
class CORE_EXPORT Performance : public EventTargetWithInlineData { class CORE_EXPORT Performance : public EventTargetWithInlineData {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
...@@ -269,6 +270,8 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -269,6 +270,8 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
const ScriptValue& detail, const ScriptValue& detail,
ExceptionState&); ExceptionState&);
void CopySecondaryBuffer();
protected: protected:
Performance(TimeTicks time_origin, Performance(TimeTicks time_origin,
scoped_refptr<base::SingleThreadTaskRunner>); scoped_refptr<base::SingleThreadTaskRunner>);
...@@ -279,8 +282,8 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -279,8 +282,8 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
return nullptr; return nullptr;
} }
bool IsResourceTimingBufferFull(); bool CanAddResourceTimingEntry();
void AddResourceTimingBuffer(PerformanceEntry&); void FireResourceTimingBufferFull(TimerBase*);
void NotifyObserversOfEntry(PerformanceEntry&) const; void NotifyObserversOfEntry(PerformanceEntry&) const;
void NotifyObserversOfEntries(PerformanceEntryVector&); void NotifyObserversOfEntries(PerformanceEntryVector&);
...@@ -290,7 +293,13 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -290,7 +293,13 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
virtual void BuildJSONValue(V8ObjectBuilder&) const; virtual void BuildJSONValue(V8ObjectBuilder&) const;
PerformanceEntryVector resource_timing_buffer_; PerformanceEntryVector resource_timing_buffer_;
unsigned resource_timing_buffer_size_; // The secondary RT buffer, used to store incoming entries after the main
// buffer is full, until the resourcetimingbufferfull event fires.
PerformanceEntryDeque resource_timing_secondary_buffer_;
unsigned resource_timing_buffer_size_limit_;
// A flag indicating that the buffer became full, the appropriate event was
// queued, but haven't yet fired.
bool resource_timing_buffer_full_event_pending_ = false;
PerformanceEntryVector event_timing_buffer_; PerformanceEntryVector event_timing_buffer_;
unsigned event_timing_buffer_max_size_; unsigned event_timing_buffer_max_size_;
Member<PerformanceEntry> navigation_timing_; Member<PerformanceEntry> navigation_timing_;
...@@ -305,7 +314,9 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -305,7 +314,9 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
HeapLinkedHashSet<TraceWrapperMember<PerformanceObserver>> observers_; HeapLinkedHashSet<TraceWrapperMember<PerformanceObserver>> observers_;
HeapLinkedHashSet<Member<PerformanceObserver>> active_observers_; HeapLinkedHashSet<Member<PerformanceObserver>> active_observers_;
HeapLinkedHashSet<Member<PerformanceObserver>> suspended_observers_; HeapLinkedHashSet<Member<PerformanceObserver>> suspended_observers_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
TaskRunnerTimer<Performance> deliver_observations_timer_; TaskRunnerTimer<Performance> deliver_observations_timer_;
TaskRunnerTimer<Performance> resource_timing_buffer_full_timer_;
}; };
} // namespace blink } // namespace blink
......
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="help" href="https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize">
<title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
let eventFired = false;
let loadRandomResource = () => {
return fetch(window.location.href + "?" + Math.random());
}
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(100);
});
let loadResourcesToFillFutureBuffer = () => {
return new Promise(resolve => {
// Gather up 3 Resource Entries to kick off the rest of test behavior.
let resources = 0;
let observer = new PerformanceObserver(function(list) {
resources += list.getEntriesByType("resource").length;
if (resources !== 3)
return;
observer.disconnect();
resolve();
});
observer.observe({entryTypes: ["resource"]});
for (let i = 0; i < 3; ++i)
loadRandomResource();
});
};
let setBufferFullEventAndBufferSize = () => {
performance.setResourceTimingBufferSize(3);
performance.onresourcetimingbufferfull = function() {
eventFired = true;
performance.clearResourceTimings();
};
};
let clearAndAddAnotherEntryToBuffer = () => {
return new Promise(resolve => {
performance.clearResourceTimings();
loadRandomResource().then(resolve);
});
};
let testThatEntryWasAdded = () => {
return new Promise((resolve, reject) => {
let waitForIt = function() {
if (performance.getEntriesByType("resource").length) {
resolve();
} else {
reject("After buffer full, entry never added to primary");
}
}
step_timeout(waitForIt, 0);
});
};
promise_test(async () => {
await loadResourcesToFillFutureBuffer();
setBufferFullEventAndBufferSize();
// Overflow the buffer.
await loadRandomResource();
await waitForEventToFire();
await clearAndAddAnotherEntryToBuffer();
await testThatEntryWasAdded();
}, "Test that entry was added to the buffer after a buffer full event");
</script>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 1;
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(resource_timing_buffer_size);
});
let overflowTheBufferAndWaitForEvent = () => {
return new Promise(resolve => {
var add_entry = () => {
performance.setResourceTimingBufferSize(resource_timing_buffer_size + 1);
// The sync entry is added to the secondary buffer, so will be the last one there and eventually dropped.
xhrScript("resources/empty.js?xhr");
resolve();
}
performance.addEventListener('resourcetimingbufferfull', add_entry);
// This resource overflows the entry buffer, and goes into the secondary buffer.
appendScript('resources/empty_script.js');
});
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 2,
'Both entries should be stored in resource timing buffer since its increases size once it overflows.');
assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
assert_true(entries[1].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithSingleResource("resources/empty.js");
await overflowTheBufferAndWaitForEvent();
// TODO(yoav): Figure out why this task is needed
await waitForNextTask();
testThatBufferContainsTheRightResources();
}, "Test that entries synchronously added to the buffer during the callback are dropped");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 1;
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(resource_timing_buffer_size);
});
let overflowTheBufferAndWaitForEvent = () => {
return new Promise(resolve => {
var add_entry = () => {
performance.setResourceTimingBufferSize(resource_timing_buffer_size + 2);
xhrScript("resources/empty.js?xhr");
resolve();
}
performance.addEventListener('resourcetimingbufferfull', add_entry);
// This resource overflows the entry buffer, and goes into the secondary buffer.
appendScript('resources/empty_script.js');
});
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 3,
'All entries should be stored in resource timing buffer since its increases size once it overflows.');
assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
assert_true(entries[1].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
assert_true(entries[2].name.includes('empty.js?xhr'), "empty.js?xhr is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithSingleResource("resources/empty.js");
await overflowTheBufferAndWaitForEvent();
await waitForNextTask();
testThatBufferContainsTheRightResources();
}, "Test that entries synchronously added to the buffer during the callback don't get dropped if the buffer is increased");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 1;
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(resource_timing_buffer_size);
performance.addEventListener('resourcetimingbufferfull', () => { assert_unreached("resourcetimingbufferfull should not fire")});
});
let overflowTheBuffer = () => {
// These resources overflow the entry buffer, and go into the secondary buffer.
xhrScript('resources/empty.js?xhr2');
xhrScript('resources/empty.js?xhr3');
performance.clearResourceTimings();
performance.setResourceTimingBufferSize(3);
xhrScript('resources/empty.js?xhr4');
window.entriesAfterAddition = performance.getEntriesByType('resource');
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 3,
'the last 3 resources should be in the buffer, since the first one was cleared');
assert_true(entries[0].name.includes('empty.js?xhr2'), "empty.js?xhr2 is in the entries buffer");
assert_true(entries[1].name.includes('empty.js?xhr3'), "empty.js?xhr3 is in the entries buffer");
assert_true(entries[2].name.includes('empty.js?xhr4'), "empty.js?xhr4 is in the entries buffer");
assert_equals(entriesAfterAddition.length, 0, "No entries should have been added to the primary buffer before the task to 'fire a buffer full event'.");
};
promise_test(async () => {
await fillUpTheBufferWithSingleResource("resources/empty.js");
overflowTheBuffer();
await waitForNextTask();
testThatBufferContainsTheRightResources();
}, "Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that decreasing the buffer size in onresourcetimingbufferfull callback does not result in extra entries being dropped.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 2;
let eventFired = false;
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(resource_timing_buffer_size);
let resize = () => {
performance.setResourceTimingBufferSize(resource_timing_buffer_size - 1);
eventFired = true;
}
performance.addEventListener('resourcetimingbufferfull', resize);
});
let overflowTheBuffer = () => {
return new Promise(resolve => {
// This resource overflows the entry buffer, and goes into the secondary buffer.
// Since the buffer size doesn't increase, it will eventually be dropped.
appendScript('resources/empty_script.js', resolve);
});
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 2,
'Both entries should be stored in resource timing buffer since it decreased its limit only after it overflowed.');
assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
assert_true(entries[1].name.includes('empty.js?second'), "empty.js?second is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithTwoResources('resources/empty.js');
await overflowTheBuffer();
await waitForEventToFire();
testThatBufferContainsTheRightResources();
}, "Test that decreasing the buffer limit during the callback does not drop entries");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates increasing the buffer size in onresourcetimingbufferfull callback of resource timing.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 1;
let eventFired = false;
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(resource_timing_buffer_size);
var increase = function() {
performance.setResourceTimingBufferSize(resource_timing_buffer_size * 2);
eventFired = true;
}
performance.addEventListener('resourcetimingbufferfull', increase);
});
let overflowTheBuffer = () => {
return new Promise(resolve => {
// This resource overflows the entry buffer, and goes into the secondary buffer.
appendScript('resources/empty_script.js', resolve);
});
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 2,
'Both entries should be stored in resource timing buffer since its increases size once it overflows.');
assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
assert_true(entries[1].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithSingleResource("resources/empty.js");
await overflowTheBuffer();
await waitForEventToFire();
testThatBufferContainsTheRightResources();
}, "Test that increasing the buffer during the callback is enough for entries not to be dropped");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates the buffer doesn't contain more entries than it should inside onresourcetimingbufferfull callback.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
let resource_timing_buffer_size = 2;
let eventFired = false;
setup(() => {
clearBufferAndSetSize(resource_timing_buffer_size);
var resize = function() {
assert_equals(performance.getEntriesByType("resource").length, resource_timing_buffer_size, "resource timing buffer in resourcetimingbufferfull is the size of the limit");
++resource_timing_buffer_size;
performance.setResourceTimingBufferSize(resource_timing_buffer_size);
xhrScript("resources/empty.js?xhr");
assert_equals(performance.getEntriesByType("resource").length, resource_timing_buffer_size - 1, "A sync request was not added to the primary buffer just yet, because it is full");
++resource_timing_buffer_size;
performance.setResourceTimingBufferSize(resource_timing_buffer_size);
eventFired = true;
}
performance.addEventListener('resourcetimingbufferfull', resize);
});
let overflowTheBuffer = () => {
return new Promise(resolve => {
// This resource overflows the entry buffer, and goes into the secondary buffer.
appendScript('resources/empty_script.js', resolve);
});
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, resource_timing_buffer_size,
'All 4 entries should be stored in resource timing buffer.');
assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
assert_true(entries[1].name.includes('empty.js?second'), "empty.js?second is in the entries buffer");
assert_true(entries[2].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
assert_true(entries[3].name.includes('empty.js?xhr'), "empty.js?xhr is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithTwoResources('resources/empty.js');
await overflowTheBuffer();
await waitForEventToFire();
testThatBufferContainsTheRightResources();
}, "Test that entries in the secondary buffer are not exposed during the callback and before they are copied to the primary buffer");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="help" href="https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize">
<title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
let eventFired = false;
let loadRandomResource = () => {
return fetch(window.location.href + "?" + Math.random());
};
setup(() => {
// Get the browser into a consistent state.
clearBufferAndSetSize(100);
window.result = "";
});
let fillUpTheBuffer = () => {
return new Promise(resolve => {
// Gather up 3 Resource Entries to kick off the rest of test behavior.
let resources = 0;
let observer = new PerformanceObserver(list => {
resources += list.getEntriesByType("resource").length;
if (resources !== 3)
return;
observer.disconnect();
resolve();
});
observer.observe({entryTypes: ["resource"]});
for (let i = 0; i < 3; ++i)
loadRandomResource();
});
};
let setBufferSize = () => {
performance.onresourcetimingbufferfull = () => {
eventFired = true;
window.result += "Event Fired with " + performance.getEntriesByType("resource").length + " entries. ";
performance.clearResourceTimings();
};
window.result += "before setLimit(3). ";
performance.setResourceTimingBufferSize(3);
window.result += "after setLimit(3). ";
};
let overflowTheBuffer = () => {
return new Promise(resolve => {
loadRandomResource().then(() => {
window.result += "after loading 4th resource. ";
resolve();
});
});
};
let checkResult = () => {
return new Promise((resolve, reject) => {
if (window.result != "before setLimit(3). after setLimit(3). after loading 4th resource. Event Fired with 3 entries. ") {
reject("Non matching value: " + window.result);
}
let entries = performance.getEntriesByType("resource");
if (entries.length != 1) {
reject("Number of entries in resource timing buffer is unexpected: " + entries.length);
}
resolve();
});
};
promise_test(async () => {
await fillUpTheBuffer();
setBufferSize();
await overflowTheBuffer();
await waitForEventToFire();
await checkResult();
}, "Test that entries added and event firing happened in the right sequence");
</script>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing.</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 1;
let global_buffer = [];
let eventFired = false;
setup(() => {
clearBufferAndSetSize(resource_timing_buffer_size);
let store_and_clear = function() {
const entryList = performance.getEntriesByType('resource');
entryList.forEach(function (entry) {
global_buffer.push(entry);
});
performance.clearResourceTimings();
eventFired = true;
}
performance.addEventListener('resourcetimingbufferfull', store_and_clear);
});
let overflowTheBuffer = () => {
return new Promise(resolve => {
// This resource overflows the entry buffer, and goes into the secondary buffer.
appendScript('resources/empty_script.js', resolve);
});
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 1,
"Only the last entry should be stored in resource timing buffer since it's cleared once it overflows.");
assert_equals(global_buffer.length, 1, '1 resource timing entry should be moved to global buffer.');
assert_true(global_buffer[0].name.includes('empty.js'), "empty.js is in the global buffer");
assert_true(entries[0].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithSingleResource("resources/empty.js");
await overflowTheBuffer();
await waitForEventToFire();
testThatBufferContainsTheRightResources();
}, "Test that entries overflowing the buffer trigger the buffer full event, can be stored, and find themselves in the primary buffer after it's cleared.");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 1;
setup(() => {
clearBufferAndSetSize(resource_timing_buffer_size);
performance.addEventListener('resourcetimingbufferfull', () => { assert_unreached("resourcetimingbufferfull should not fire"); });
});
let overflowTheBuffer = () => {
// These resources overflow the entry buffer, and go into the secondary buffer.
xhrScript('resources/empty.js?xhr2');
xhrScript('resources/empty.js?xhr3');
performance.setResourceTimingBufferSize(3);
};
let testThatBufferContainsTheRightResources = () => {
let entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 3,
'All resources should be in the buffer, since its size was increased');
assert_true(entries[0].name.includes('empty.js'), "empty.js?xhr2 is in the entries buffer");
assert_true(entries[1].name.includes('empty.js?xhr2'), "empty.js?xhr3 is in the entries buffer");
assert_true(entries[2].name.includes('empty.js?xhr3'), "empty.js?xhr3 is in the entries buffer");
};
promise_test(async () => {
await fillUpTheBufferWithSingleResource("resources/empty.js");
overflowTheBuffer();
await waitForNextTask();
testThatBufferContainsTheRightResources();
}, "Test that overflowing the buffer and immediately increasing its limit does not trigger the resourcetimingbufferfull event");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
const resource_timing_buffer_size = 2;
let bufferFullCount = 0;
let eventFired = false;
setup(() => {
clearBufferAndSetSize(resource_timing_buffer_size);
performance.addEventListener('resourcetimingbufferfull', e => {
assert_equals(e.bubbles, false, "Event bubbles attribute is false");
bufferFullCount++;
eventFired = true;
});
});
let overflowTheBuffer = () => {
return new Promise(resolve => {
// This resource overflows the entry buffer, and goes into the secondary buffer.
appendScript('resources/empty_script.js', resolve);
});
};
let testThatBufferContainsTheRightResources = () => {
assert_equals(performance.getEntriesByType('resource').length, resource_timing_buffer_size, 'There should only be |bufferSize| resource entries.');
assert_equals(bufferFullCount, 1, 'onresourcetimingbufferfull should have been invoked once.');
};
promise_test(async () => {
await fillUpTheBufferWithTwoResources('resources/empty.js');
await overflowTheBuffer();
await waitForEventToFire();
testThatBufferContainsTheRightResources();
}, "Test that a buffer full event does not bubble and that resourcetimingbufferfull is called only once per overflow");
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/webperftestharness.js"></script>
<script src="resources/webperftestharnessextension.js"></script>
</head>
<body onload=onload_test()>
<script>
const context = new PerformanceContext(performance);
const bufferSize = 5;
context.setResourceTimingBufferSize(bufferSize);
let bufferFullCount = 0;
function buffer_full_callback() {
bufferFullCount++;
}
context.registerResourceTimingBufferFullCallback(buffer_full_callback);
// Scripts appended in JS to ensure setResourceTimingBufferSize is called before.
function appendScript(src) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
document.body.appendChild(script);
}
appendScript('resources/empty.js');
appendScript('resources/empty_script.js');
appendScript('resources/resource_timing_test0.js');
setup({ explicit_done: true });
function onload_test() {
test_equals(context.getEntriesByType('resource').length, bufferSize, 'There should only be |bufferSize| resource entries.');
test_equals(bufferFullCount, 1, 'onresourcetimingbufferfull should have been invoked once buffer is full.');
done();
}
</script>
</body>
</html>
This is a testharness.js-based test.
PASS There are 4 scripts, and setResourceTimingBufferSize does not reduce the size.
FAIL onresourcetimingbufferfull should not be invoked during setResourceTimingBufferSize. assert_equals: onresourcetimingbufferfull should not be invoked during setResourceTimingBufferSize. expected 0 but got 2
Harness: the test ran to completion.
This is a testharness.js-based test.
PASS No entry should be stored in resource timing buffer since its cleared once an item arrived.
FAIL 6 resource timing entries should be moved to global buffer. assert_equals: 6 resource timing entries should be moved to global buffer. expected 6 but got 7
FAIL http://web-platform.test:8001/resource-timing/resources/empty.js is not expected to be in the Resource Timing buffer assert_unreached: Reached unreachable code
PASS http://web-platform.test:8001/resource-timing/resources/empty_script.js is expected to have initiatorType script
PASS http://web-platform.test:8001/resource-timing/resources/resource_timing_test0.js is expected to have initiatorType script
PASS http://web-platform.test:8001/resource-timing/resources/webperftestharness.js is expected to have initiatorType script
PASS http://web-platform.test:8001/resource-timing/resources/webperftestharnessextension.js is expected to have initiatorType script
PASS http://web-platform.test:8001/resources/testharness.js is expected to have initiatorType script
PASS http://web-platform.test:8001/resources/testharnessreport.js is expected to have initiatorType script
Harness: the test ran to completion.
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing.</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/webperftestharness.js"></script>
<script src="resources/webperftestharnessextension.js"></script>
</head>
<body onload=onload_test()>
<script>
const context = new PerformanceContext(performance);
const resource_timing_buffer_size = 1;
let global_buffer = [];
function store_and_clear() {
const entryList = context.getEntriesByType('resource');
entryList.forEach(function (entry) {
global_buffer.push(entry);
});
context.clearResourceTimings();
}
context.registerResourceTimingBufferFullCallback(store_and_clear);
context.setResourceTimingBufferSize(resource_timing_buffer_size);
// Scripts appended in JS to ensure setResourceTimingBufferSize is called before.
function appendScript(src) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
document.body.appendChild(script);
}
appendScript('resources/empty.js');
appendScript('resources/empty_script.js');
appendScript('resources/resource_timing_test0.js');
setup({ explicit_done: true });
function onload_test() {
test_equals(context.getEntriesByType('resource').length, 0, 'No entry should be stored in resource timing buffer since its cleared once an item arrived.');
// The entry for empty.js must not be in the global buffer, but all others should be.
test_equals(global_buffer.length, 6, '6 resource timing entries should be moved to global buffer.');
const index = window.location.pathname.lastIndexOf('resource-timing');
const pathname = window.location.pathname.substring(0, index);
let expected_entries = {};
expected_entries[pathname + 'resources/testharness.js'] = 'script';
expected_entries[pathname + 'resources/testharnessreport.js'] = 'script';
expected_entries[pathname + 'resource-timing/resources/webperftestharness.js'] = 'script';
expected_entries[pathname + 'resource-timing/resources/webperftestharnessextension.js'] = 'script';
expected_entries[pathname + 'resource-timing/resources/empty_script.js'] = 'script';
expected_entries[pathname + 'resource-timing/resources/resource_timing_test0.js'] = 'script';
test_resource_entries(global_buffer, expected_entries);
done();
}
</script>
</body>
</html>
let appendScript = (src, resolve) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.onload = resolve;
document.body.appendChild(script);
}
let xhrScript = src => {
var xhr = new XMLHttpRequest();
xhr.open("GET", src, false);
xhr.send(null);
}
let waitForNextTask = () => {
return new Promise(resolve => {
step_timeout(resolve, 0);
});
};
let waitForEventToFire = () => {
return new Promise(resolve => {
let waitForIt = function() {
if (eventFired) {
eventFired = false;
resolve();
} else {
step_timeout(waitForIt, 0);
}
}
step_timeout(waitForIt, 0);
});
};
let clearBufferAndSetSize = size => {
performance.clearResourceTimings();
performance.setResourceTimingBufferSize(size);
}
let fillUpTheBufferWithSingleResource = src => {
return new Promise(resolve => {
// This resource gets buffered in the resource timing entry buffer.
appendScript(src, resolve);
});
};
let loadResource = src => {
return new Promise(resolve => {
appendScript(src, resolve);
});
};
let fillUpTheBufferWithTwoResources = async src => {
// These resources get buffered in the resource timing entry buffer.
await loadResource(src);
await loadResource(src + '?second');
};
...@@ -44,19 +44,13 @@ promise_test(function(test) { ...@@ -44,19 +44,13 @@ promise_test(function(test) {
assert_greater_than(entry.startTime, 0); assert_greater_than(entry.startTime, 0);
assert_greater_than(entry.responseEnd, entry.startTime); assert_greater_than(entry.responseEnd, entry.startTime);
} }
return Promise.race([ return new Promise(function(resolve) {
new Promise(function(resolve) {
performance.onresourcetimingbufferfull = _ => { performance.onresourcetimingbufferfull = _ => {
resolve('bufferfull'); resolve('bufferfull');
} }
performance.setResourceTimingBufferSize(expectedResources.length); performance.setResourceTimingBufferSize(expectedResources.length);
}), fetch('dummy.txt');
});
// Race the bufferfull event against another fetch. We should get the
// event before this completes. This allows us to detect a failure
// to dispatch the event without timing out the entire test.
fetch('dummy.txt').then(resp => resp.text())
]);
}) })
.then(function(result) { .then(function(result) {
assert_equals(result, 'bufferfull'); assert_equals(result, 'bufferfull');
......
<script>
if (window.testRunner)
testRunner.dumpAsText();
performance.onresourcetimingbufferfull = function() {
document.body.innerHTML = "PASS";
};
performance.setResourceTimingBufferSize(1);
</script>
<div style='border-image-source: url(#1); background-image: url(#2);'>
<script>
if (window.testRunner)
testRunner.dumpAsText();
performance.onresourcetimingbufferfull = function() {
document.body.appendChild(document.createTextNode("PASS"));
};
performance.setResourceTimingBufferSize(1);
</script>
<iframe src="resources/content-iframe.html"></iframe>
PASS successfullyParsed is true
TEST COMPLETE
PASS. No crash when stop loading on resource timing buffer full.
<html>
<body>
<script src="/js-test-resources/js-test.js"></script>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
performance.addEventListener('resourcetimingbufferfull', function(event) {
document.body.innerHTML = "PASS. No crash when stop loading on resource timing buffer full.";
setTimeout("testRunner.notifyDone()", 0);
});
performance.setResourceTimingBufferSize(1);
</script>
</body>
</html>
<html>
<head>
<link rel="help" href="https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/ResourceTiming/Overview.html#extensions-performance-interface">
<script src="/js-test-resources/js-test.js"></script>
<script>
description("This test checks that Performance inherits EventTarget and that addEventListener() works for resourcetimingbufferfull events.");
window.jsTestIsAsync = true;
var bufferFullCount = 0;
var parameter;
function onBufferFull(event) {
shouldBeNull('performance.onresourcetimingbufferfull');
parameter = event;
shouldBe('parameter.__proto__', 'Event.prototype');
bufferFullCount++;
}
shouldBe('Performance.prototype.__proto__', 'EventTarget.prototype');
performance.addEventListener('resourcetimingbufferfull', onBufferFull);
shouldBeNull('performance.onresourcetimingbufferfull');
performance.setResourceTimingBufferSize(2);
</script>
</head>
<body>
<script>
function test() {
// Make sure the onBufferFull callback was called exactly 1 time.
shouldBe('bufferFullCount', '1');
performance.removeEventListener('resourcetimingbufferfull', onBufferFull);
finishJSTest();
}
window.onload = test;
</script>
<script src="resources/empty-script.js"></script>
</body>
</html>
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
// Scripts appended in JS to ensure setResourceTimingBufferSize is called before. // Scripts appended in JS to ensure setResourceTimingBufferSize is called before.
let counter = performance.getEntriesByType("resource").length; let counter = performance.getEntriesByType("resource").length;
const documentFragment = document.createDocumentFragment(); const documentFragment = document.createDocumentFragment();
while (counter < default_buffer_size) { while (counter <= default_buffer_size) {
const src = "../loading/resources/empty.js?" + counter; const src = "../loading/resources/empty.js?" + counter;
const script = document.createElement('script'); const script = document.createElement('script');
script.type = 'text/javascript'; script.type = 'text/javascript';
......
...@@ -55,6 +55,7 @@ promise_test(function(test) { ...@@ -55,6 +55,7 @@ promise_test(function(test) {
return new Promise(function(resolve) { return new Promise(function(resolve) {
performance.onresourcetimingbufferfull = resolve; performance.onresourcetimingbufferfull = resolve;
performance.setResourceTimingBufferSize(expectedResources.length); performance.setResourceTimingBufferSize(expectedResources.length);
fetch('../../resources/dummy.txt');
}); });
}) })
.then(function() { .then(function() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment