blob: bf0950208a0ebb8f572d3ffc3ee82d625c9fe865 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/timing/window_performance.h"
#include <algorithm>
#include "base/ranges/algorithm.h"
#include "base/test/metrics/histogram_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_performance_observer_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_performance_observer_init.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/performance_entry_names.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/timing/back_forward_cache_restoration.h"
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/performance_long_task_timing.h"
#include "third_party/blink/renderer/core/timing/performance_observer.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
namespace blink {
namespace {
constexpr int kTimeOrigin = 1;
constexpr int kEvent1Time = 123;
constexpr int kEvent1PageshowStart = 456;
constexpr int kEvent1PageshowEnd = 789;
constexpr int kEvent2Time = 321;
constexpr int kEvent2PageshowStart = 654;
constexpr int kEvent2PageshowEnd = 987;
} // namespace
class LocalDOMWindow;
class TestPerformance : public Performance {
public:
explicit TestPerformance(ScriptState* script_state)
: Performance(base::TimeTicks() + base::Milliseconds(kTimeOrigin),
ExecutionContext::From(script_state)
->CrossOriginIsolatedCapability(),
ExecutionContext::From(script_state)
->GetTaskRunner(TaskType::kPerformanceTimeline)),
execution_context_(ExecutionContext::From(script_state)) {}
~TestPerformance() override = default;
ExecutionContext* GetExecutionContext() const override {
return execution_context_.Get();
}
uint64_t interactionCount() const override { return 0; }
int NumActiveObservers() { return active_observers_.size(); }
int NumObservers() { return observers_.size(); }
bool HasPerformanceObserverFor(PerformanceEntry::EntryType entry_type) {
return HasObserverFor(entry_type);
}
void Trace(Visitor* visitor) const override {
Performance::Trace(visitor);
visitor->Trace(execution_context_);
}
private:
Member<ExecutionContext> execution_context_;
};
class PerformanceTest : public PageTestBase {
protected:
~PerformanceTest() override { execution_context_->NotifyContextDestroyed(); }
void Initialize(ScriptState* script_state) {
v8::Local<v8::Function> callback =
v8::Function::New(script_state->GetContext(), nullptr).ToLocalChecked();
base_ = MakeGarbageCollected<TestPerformance>(script_state);
cb_ = V8PerformanceObserverCallback::Create(callback);
observer_ = MakeGarbageCollected<PerformanceObserver>(
ExecutionContext::From(script_state), base_, cb_);
}
void SetUp() override {
PageTestBase::SetUp();
execution_context_ = MakeGarbageCollected<NullExecutionContext>();
}
ExecutionContext* GetExecutionContext() { return execution_context_.Get(); }
int NumPerformanceEntriesInObserver() {
return observer_->performance_entries_.size();
}
PerformanceEntryVector PerformanceEntriesInObserver() {
return observer_->performance_entries_;
}
void CheckBackForwardCacheRestoration(PerformanceEntryVector entries) {
// Expect there are 2 back forward cache restoration entries.
EXPECT_EQ(2, base::ranges::count(entries, "back-forward-cache-restoration",
&PerformanceEntry::entryType));
// Retain only back forward cache restoration entries.
entries.erase(std::remove_if(entries.begin(), entries.end(),
[](const PerformanceEntry* e) -> bool {
return e->entryType() !=
"back-forward-cache-restoration";
}),
entries.end());
BackForwardCacheRestoration* b1 =
static_cast<BackForwardCacheRestoration*>(entries[0].Get());
EXPECT_EQ(kEvent1Time - kTimeOrigin, b1->startTime());
EXPECT_EQ(kEvent1PageshowStart - kTimeOrigin, b1->pageshowEventStart());
EXPECT_EQ(kEvent1PageshowEnd - kTimeOrigin, b1->pageshowEventEnd());
BackForwardCacheRestoration* b2 =
static_cast<BackForwardCacheRestoration*>(entries[1].Get());
EXPECT_EQ(kEvent2Time - kTimeOrigin, b2->startTime());
EXPECT_EQ(kEvent2PageshowStart - kTimeOrigin, b2->pageshowEventStart());
EXPECT_EQ(kEvent2PageshowEnd - kTimeOrigin, b2->pageshowEventEnd());
}
Persistent<TestPerformance> base_;
Persistent<ExecutionContext> execution_context_;
Persistent<PerformanceObserver> observer_;
Persistent<V8PerformanceObserverCallback> cb_;
};
TEST_F(PerformanceTest, Register) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
EXPECT_EQ(0, base_->NumObservers());
EXPECT_EQ(0, base_->NumActiveObservers());
base_->RegisterPerformanceObserver(*observer_.Get());
EXPECT_EQ(1, base_->NumObservers());
EXPECT_EQ(0, base_->NumActiveObservers());
base_->UnregisterPerformanceObserver(*observer_.Get());
EXPECT_EQ(0, base_->NumObservers());
EXPECT_EQ(0, base_->NumActiveObservers());
}
TEST_F(PerformanceTest, Activate) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
EXPECT_EQ(0, base_->NumObservers());
EXPECT_EQ(0, base_->NumActiveObservers());
base_->RegisterPerformanceObserver(*observer_.Get());
EXPECT_EQ(1, base_->NumObservers());
EXPECT_EQ(0, base_->NumActiveObservers());
base_->ActivateObserver(*observer_.Get());
EXPECT_EQ(1, base_->NumObservers());
EXPECT_EQ(1, base_->NumActiveObservers());
base_->UnregisterPerformanceObserver(*observer_.Get());
EXPECT_EQ(0, base_->NumObservers());
EXPECT_EQ(1, base_->NumActiveObservers());
}
TEST_F(PerformanceTest, AddLongTaskTiming) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
// Add a long task entry, but no observer registered.
base_->AddLongTaskTiming(base::TimeTicks() + base::Seconds(1234),
base::TimeTicks() + base::Seconds(5678),
AtomicString("window"), AtomicString("same-origin"),
AtomicString("www.foo.com/bar"), g_empty_atom,
g_empty_atom);
EXPECT_FALSE(base_->HasPerformanceObserverFor(PerformanceEntry::kLongTask));
EXPECT_EQ(0, NumPerformanceEntriesInObserver()); // has no effect
// Make an observer for longtask
NonThrowableExceptionState exception_state;
PerformanceObserverInit* options = PerformanceObserverInit::Create();
Vector<String> entry_type_vec;
entry_type_vec.push_back("longtask");
options->setEntryTypes(entry_type_vec);
observer_->observe(scope.GetScriptState(), options, exception_state);
EXPECT_TRUE(base_->HasPerformanceObserverFor(PerformanceEntry::kLongTask));
// Add a long task entry
base_->AddLongTaskTiming(base::TimeTicks() + base::Seconds(1234),
base::TimeTicks() + base::Seconds(5678),
AtomicString("window"), AtomicString("same-origin"),
AtomicString("www.foo.com/bar"), g_empty_atom,
g_empty_atom);
EXPECT_EQ(1, NumPerformanceEntriesInObserver()); // added an entry
}
TEST_F(PerformanceTest, BackForwardCacheRestoration) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
NonThrowableExceptionState exception_state;
PerformanceObserverInit* options = PerformanceObserverInit::Create();
Vector<String> entry_type_vec;
entry_type_vec.push_back("back-forward-cache-restoration");
options->setEntryTypes(entry_type_vec);
observer_->observe(scope.GetScriptState(), options, exception_state);
EXPECT_TRUE(base_->HasPerformanceObserverFor(
PerformanceEntry::kBackForwardCacheRestoration));
base_->AddBackForwardCacheRestoration(
base::TimeTicks() + base::Milliseconds(kEvent1Time),
base::TimeTicks() + base::Milliseconds(kEvent1PageshowStart),
base::TimeTicks() + base::Milliseconds(kEvent1PageshowEnd));
base_->AddBackForwardCacheRestoration(
base::TimeTicks() + base::Milliseconds(kEvent2Time),
base::TimeTicks() + base::Milliseconds(kEvent2PageshowStart),
base::TimeTicks() + base::Milliseconds(kEvent2PageshowEnd));
auto entries = PerformanceEntriesInObserver();
CheckBackForwardCacheRestoration(entries);
entries = base_->getEntries();
CheckBackForwardCacheRestoration(entries);
entries = base_->getEntriesByType(
performance_entry_names::kBackForwardCacheRestoration);
CheckBackForwardCacheRestoration(entries);
}
// Validate ordering after insertion into an empty vector.
TEST_F(PerformanceTest, InsertEntryOnEmptyBuffer) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
PerformanceEntryVector test_buffer_;
PerformanceEventTiming* test_entry = PerformanceEventTiming::Create(
AtomicString("event"), 0.0, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
Performance::kDoNotRecordSwaps);
PerformanceEntryVector sorted_buffer_;
sorted_buffer_.push_back(*test_entry);
EXPECT_EQ(test_buffer_, sorted_buffer_);
}
// Validate ordering after insertion into a non-empty vector.
TEST_F(PerformanceTest, InsertEntryOnExistingBuffer) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
PerformanceEntryVector test_buffer_;
// Insert 3 entries into the vector.
for (int i = 0; i < 3; i++) {
double tmp = 1.0;
PerformanceEventTiming* entry = PerformanceEventTiming::Create(
AtomicString("event"), tmp * i, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
test_buffer_.push_back(*entry);
}
PerformanceEventTiming* test_entry = PerformanceEventTiming::Create(
AtomicString("event"), 1.0, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
// Create copy of the test_buffer_.
PerformanceEntryVector sorted_buffer_ = test_buffer_;
base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
Performance::kDoNotRecordSwaps);
sorted_buffer_.push_back(*test_entry);
std::sort(sorted_buffer_.begin(), sorted_buffer_.end(),
PerformanceEntry::StartTimeCompareLessThan);
EXPECT_EQ(test_buffer_, sorted_buffer_);
}
// Validate ordering when inserting to the front of a buffer.
TEST_F(PerformanceTest, InsertEntryToFrontOfBuffer) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
PerformanceEntryVector test_buffer_;
// Insert 3 entries into the vector.
for (int i = 0; i < 3; i++) {
double tmp = 1.0;
PerformanceEventTiming* entry = PerformanceEventTiming::Create(
AtomicString("event"), tmp * i, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
test_buffer_.push_back(*entry);
}
PerformanceEventTiming* test_entry = PerformanceEventTiming::Create(
AtomicString("event"), 0.0, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
// Create copy of the test_buffer_.
PerformanceEntryVector sorted_buffer_ = test_buffer_;
base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
Performance::kDoNotRecordSwaps);
sorted_buffer_.push_back(*test_entry);
std::sort(sorted_buffer_.begin(), sorted_buffer_.end(),
PerformanceEntry::StartTimeCompareLessThan);
EXPECT_EQ(test_buffer_, sorted_buffer_);
}
TEST_F(PerformanceTest, MergePerformanceEntryVectorsTest) {
V8TestingScope scope;
Initialize(scope.GetScriptState());
PerformanceEntryVector first_vector;
PerformanceEntryVector second_vector;
PerformanceEntryVector test_vector;
for (int i = 0; i < 6; i += 2) {
double tmp = 1.0;
PerformanceEventTiming* entry = PerformanceEventTiming::Create(
AtomicString("event"), tmp * i, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
first_vector.push_back(*entry);
test_vector.push_back(*entry);
}
for (int i = 1; i < 6; i += 2) {
double tmp = 1.0;
PerformanceEventTiming* entry = PerformanceEventTiming::Create(
AtomicString("event"), tmp * i, 0.0, 0.0, false, nullptr,
LocalDOMWindow::From(scope.GetScriptState()));
second_vector.push_back(*entry);
test_vector.push_back(*entry);
}
PerformanceEntryVector all_entries;
all_entries =
MergePerformanceEntryVectors(all_entries, first_vector, g_null_atom);
all_entries =
MergePerformanceEntryVectors(all_entries, second_vector, g_null_atom);
std::sort(test_vector.begin(), test_vector.end(),
PerformanceEntry::StartTimeCompareLessThan);
EXPECT_EQ(all_entries, test_vector);
}
} // namespace blink