| /* |
| * 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 "third_party/blink/renderer/core/timing/performance.h" |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/default_clock.h" |
| #include "base/time/default_tick_clock.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "third_party/blink/public/mojom/permissions_policy/document_policy_feature.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_performance_mark_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_performance_measure_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_init_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_union_double_string.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_union_performancemeasureoptions_string.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/document_timing.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/event_target_names.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/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/loader/document_load_timing.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/timing/back_forward_cache_restoration.h" |
| #include "third_party/blink/renderer/core/timing/background_tracing_helper.h" |
| #include "third_party/blink/renderer/core/timing/dom_window_performance.h" |
| #include "third_party/blink/renderer/core/timing/largest_contentful_paint.h" |
| #include "third_party/blink/renderer/core/timing/layout_shift.h" |
| #include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h" |
| #include "third_party/blink/renderer/core/timing/performance_element_timing.h" |
| #include "third_party/blink/renderer/core/timing/performance_entry.h" |
| #include "third_party/blink/renderer/core/timing/performance_event_timing.h" |
| #include "third_party/blink/renderer/core/timing/performance_long_task_timing.h" |
| #include "third_party/blink/renderer/core/timing/performance_mark.h" |
| #include "third_party/blink/renderer/core/timing/performance_measure.h" |
| #include "third_party/blink/renderer/core/timing/performance_observer.h" |
| #include "third_party/blink/renderer/core/timing/performance_resource_timing.h" |
| #include "third_party/blink/renderer/core/timing/performance_server_timing.h" |
| #include "third_party/blink/renderer/core/timing/performance_user_timing.h" |
| #include "third_party/blink/renderer/core/timing/profiler.h" |
| #include "third_party/blink/renderer/core/timing/profiler_group.h" |
| #include "third_party/blink/renderer/core/timing/soft_navigation_entry.h" |
| #include "third_party/blink/renderer/core/timing/time_clamper.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_timing_utils.h" |
| #include "third_party/blink/renderer/platform/network/http_parsers.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "v8/include/v8-metrics.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // LongTask API can be a source of many events. Filter on Performance object |
| // level before reporting to UKM to smooth out recorded events over all pages. |
| constexpr size_t kLongTaskUkmSampleInterval = 100; |
| |
| const char kSwapsPerInsertionHistogram[] = |
| "Renderer.Core.Timing.Performance.SwapsPerPerformanceEntryInsertion"; |
| |
| bool IsMeasureOptionsEmpty(const PerformanceMeasureOptions& options) { |
| return !options.hasDetail() && !options.hasEnd() && !options.hasStart() && |
| !options.hasDuration(); |
| } |
| |
| base::TimeDelta GetUnixAtZeroMonotonic(const base::Clock* clock, |
| const base::TickClock* tick_clock) { |
| base::TimeDelta unix_time_now = clock->Now() - base::Time::UnixEpoch(); |
| base::TimeDelta time_since_origin = tick_clock->NowTicks().since_origin(); |
| return unix_time_now - time_since_origin; |
| } |
| |
| void RecordLongTaskUkm(ExecutionContext* execution_context, |
| base::TimeDelta start_time, |
| base::TimeDelta duration) { |
| v8::metrics::LongTaskStats stats = |
| v8::metrics::LongTaskStats::Get(execution_context->GetIsolate()); |
| // TODO(cbruni, 1275056): Filter out stats without v8_execute_us. |
| ukm::builders::PerformanceAPI_LongTask(execution_context->UkmSourceID()) |
| .SetStartTime(start_time.InMilliseconds()) |
| .SetDuration(duration.InMicroseconds()) |
| .SetDuration_V8_GC(stats.gc_full_atomic_wall_clock_duration_us + |
| stats.gc_full_incremental_wall_clock_duration_us + |
| stats.gc_young_wall_clock_duration_us) |
| .SetDuration_V8_GC_Full_Atomic( |
| stats.gc_full_atomic_wall_clock_duration_us) |
| .SetDuration_V8_GC_Full_Incremental( |
| stats.gc_full_incremental_wall_clock_duration_us) |
| .SetDuration_V8_GC_Young(stats.gc_young_wall_clock_duration_us) |
| .SetDuration_V8_Execute(stats.v8_execute_us) |
| .Record(execution_context->UkmRecorder()); |
| } |
| |
| PerformanceEntry::EntryType kDroppableEntryTypes[] = { |
| PerformanceEntry::kResource, |
| PerformanceEntry::kLongTask, |
| PerformanceEntry::kElement, |
| PerformanceEntry::kEvent, |
| PerformanceEntry::kLayoutShift, |
| PerformanceEntry::kLargestContentfulPaint, |
| PerformanceEntry::kPaint, |
| PerformanceEntry::kBackForwardCacheRestoration, |
| PerformanceEntry::kSoftNavigation, |
| }; |
| |
| void SwapEntries(PerformanceEntryVector& entries, |
| int leftIndex, |
| int rightIndex) { |
| auto tmp = entries[leftIndex]; |
| entries[leftIndex] = entries[rightIndex]; |
| entries[rightIndex] = tmp; |
| } |
| |
| inline bool CheckName(const PerformanceEntry* entry, |
| const AtomicString& maybe_name) { |
| // If we're not filtering by name, then any entry matches. |
| if (!maybe_name) { |
| return true; |
| } |
| return entry->name() == maybe_name; |
| } |
| |
| // |output_entries| either gets reassigned to or is appended to. |
| // Therefore, it must point to a valid PerformanceEntryVector. |
| void FilterEntriesTriggeredBySoftNavigationIfNeeded( |
| PerformanceEntryVector& input_entries, |
| PerformanceEntryVector** output_entries, |
| bool include_soft_navigation_observations) { |
| if (include_soft_navigation_observations) { |
| *output_entries = &input_entries; |
| } else { |
| DCHECK(output_entries && *output_entries); |
| std::copy_if(input_entries.begin(), input_entries.end(), |
| std::back_inserter(**output_entries), |
| [&](const PerformanceEntry* entry) { |
| return !entry->IsTriggeredBySoftNavigation(); |
| }); |
| } |
| } |
| |
| } // namespace |
| |
| PerformanceEntryVector MergePerformanceEntryVectors( |
| const PerformanceEntryVector& first_entry_vector, |
| const PerformanceEntryVector& second_entry_vector, |
| const AtomicString& maybe_name) { |
| PerformanceEntryVector merged_entries; |
| merged_entries.reserve(first_entry_vector.size() + |
| second_entry_vector.size()); |
| |
| auto* first_it = first_entry_vector.begin(); |
| auto* first_end = first_entry_vector.end(); |
| auto* second_it = second_entry_vector.begin(); |
| auto* second_end = second_entry_vector.end(); |
| |
| // Advance the second iterator past any entries with disallowed names. |
| while (second_it != second_end && !CheckName(*second_it, maybe_name)) { |
| ++second_it; |
| } |
| |
| auto PushBackSecondIteratorAndAdvance = [&]() { |
| DCHECK(CheckName(*second_it, maybe_name)); |
| merged_entries.push_back(*second_it); |
| ++second_it; |
| while (second_it != second_end && !CheckName(*second_it, maybe_name)) { |
| ++second_it; |
| } |
| }; |
| |
| // What follows is based roughly on a reference implementation of std::merge, |
| // except that after copying a value from the second iterator, it must also |
| // advance the second iterator past any entries with disallowed names. |
| |
| while (first_it != first_end) { |
| // If the second iterator has ended, just copy the rest of the contents |
| // from the first iterator. |
| if (second_it == second_end) { |
| std::copy(first_it, first_end, std::back_inserter(merged_entries)); |
| break; |
| } |
| |
| // Add an entry to the result vector from either the first or second |
| // iterator, whichever has an earlier time. The first iterator wins ties. |
| if (PerformanceEntry::StartTimeCompareLessThan(*second_it, *first_it)) { |
| PushBackSecondIteratorAndAdvance(); |
| } else { |
| DCHECK(CheckName(*first_it, maybe_name)); |
| merged_entries.push_back(*first_it); |
| ++first_it; |
| } |
| } |
| |
| // If there are still entries in the second iterator after the first iterator |
| // has ended, copy all remaining entries that have allowed names. |
| while (second_it != second_end) { |
| PushBackSecondIteratorAndAdvance(); |
| } |
| |
| return merged_entries; |
| } |
| |
| using PerformanceObserverVector = HeapVector<Member<PerformanceObserver>>; |
| |
| constexpr size_t kDefaultResourceTimingBufferSize = 250; |
| constexpr size_t kDefaultEventTimingBufferSize = 150; |
| constexpr size_t kDefaultElementTimingBufferSize = 150; |
| constexpr size_t kDefaultLayoutShiftBufferSize = 150; |
| constexpr size_t kDefaultLargestContenfulPaintSize = 150; |
| constexpr size_t kDefaultLongTaskBufferSize = 200; |
| constexpr size_t kDefaultLongAnimationFrameBufferSize = 200; |
| constexpr size_t kDefaultBackForwardCacheRestorationBufferSize = 200; |
| constexpr size_t kDefaultSoftNavigationBufferSize = 50; |
| // Paint timing entries is more than twice as much as the soft navigation buffer |
| // size, as there can be 2 paint entries for each soft navigation, plus 2 |
| // entries for the initial navigation. |
| constexpr size_t kDefaultPaintEntriesBufferSize = |
| kDefaultSoftNavigationBufferSize * 2 + 2; |
| |
| Performance::Performance( |
| base::TimeTicks time_origin, |
| bool cross_origin_isolated_capability, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| ExecutionContext* context) |
| : resource_timing_buffer_size_limit_(kDefaultResourceTimingBufferSize), |
| back_forward_cache_restoration_buffer_size_limit_( |
| kDefaultBackForwardCacheRestorationBufferSize), |
| event_timing_buffer_max_size_(kDefaultEventTimingBufferSize), |
| element_timing_buffer_max_size_(kDefaultElementTimingBufferSize), |
| user_timing_(nullptr), |
| time_origin_(time_origin), |
| tick_clock_(base::DefaultTickClock::GetInstance()), |
| cross_origin_isolated_capability_(cross_origin_isolated_capability), |
| observer_filter_options_(PerformanceEntry::kInvalid), |
| task_runner_(std::move(task_runner)), |
| deliver_observations_timer_(task_runner_, |
| this, |
| &Performance::DeliverObservationsTimerFired), |
| resource_timing_buffer_full_timer_( |
| task_runner_, |
| this, |
| &Performance::FireResourceTimingBufferFull) { |
| unix_at_zero_monotonic_ = |
| GetUnixAtZeroMonotonic(base::DefaultClock::GetInstance(), tick_clock_); |
| // |context| may be null in tests. |
| if (context) { |
| background_tracing_helper_ = |
| MakeGarbageCollected<BackgroundTracingHelper>(context); |
| } |
| // Initialize the map of dropped entry types only with those which could be |
| // dropped (saves some unnecessary 0s). |
| for (const auto type : kDroppableEntryTypes) { |
| dropped_entries_count_map_.insert(type, 0); |
| } |
| } |
| |
| Performance::~Performance() = default; |
| |
| const AtomicString& Performance::InterfaceName() const { |
| return event_target_names::kPerformance; |
| } |
| |
| PerformanceTiming* Performance::timing() const { |
| return nullptr; |
| } |
| |
| PerformanceNavigation* Performance::navigation() const { |
| return nullptr; |
| } |
| |
| MemoryInfo* Performance::memory(ScriptState*) const { |
| return nullptr; |
| } |
| |
| EventCounts* Performance::eventCounts() { |
| return nullptr; |
| } |
| |
| ScriptPromise<MemoryMeasurement> Performance::measureUserAgentSpecificMemory( |
| ScriptState* script_state, |
| ExceptionState& exception_state) const { |
| return MeasureMemoryController::StartMeasurement(script_state, |
| exception_state); |
| } |
| |
| DOMHighResTimeStamp Performance::timeOrigin() const { |
| DCHECK(!time_origin_.is_null()); |
| base::TimeDelta time_origin_from_zero_monotonic = |
| time_origin_ - base::TimeTicks(); |
| return ClampTimeResolution( |
| unix_at_zero_monotonic_ + time_origin_from_zero_monotonic, |
| cross_origin_isolated_capability_); |
| } |
| |
| PerformanceEntryVector Performance::getEntries() { |
| return GetEntriesForCurrentFrame(); |
| } |
| |
| PerformanceEntryVector Performance::getEntries( |
| ScriptState* script_state, |
| PerformanceEntryFilterOptions* options) { |
| if (!RuntimeEnabledFeatures::CrossFramePerformanceTimelineEnabled() || |
| !options) { |
| return GetEntriesForCurrentFrame(); |
| } |
| |
| PerformanceEntryVector entries; |
| |
| AtomicString name = |
| options->hasName() ? AtomicString(options->name()) : g_null_atom; |
| |
| AtomicString entry_type = options->hasEntryType() |
| ? AtomicString(options->entryType()) |
| : g_null_atom; |
| |
| // Get sorted entry list based on provided input. |
| if (options->getIncludeChildFramesOr(false)) { |
| entries = GetEntriesWithChildFrames(script_state, entry_type, name); |
| } else { |
| if (!entry_type) { |
| entries = GetEntriesForCurrentFrame(name); |
| } else { |
| entries = GetEntriesByTypeForCurrentFrame(entry_type, name); |
| } |
| } |
| |
| return entries; |
| } |
| |
| PerformanceEntryVector Performance::GetEntriesForCurrentFrame( |
| const AtomicString& maybe_name) { |
| PerformanceEntryVector entries; |
| |
| entries = MergePerformanceEntryVectors(entries, resource_timing_buffer_, |
| maybe_name); |
| if (first_input_timing_ && CheckName(first_input_timing_, maybe_name)) { |
| InsertEntryIntoSortedBuffer(entries, *first_input_timing_, |
| kDoNotRecordSwaps); |
| } |
| // This extra checking is needed when WorkerPerformance |
| // calls this method. |
| if (navigation_timing_ && CheckName(navigation_timing_, maybe_name)) { |
| InsertEntryIntoSortedBuffer(entries, *navigation_timing_, |
| kDoNotRecordSwaps); |
| } |
| |
| if (user_timing_) { |
| if (maybe_name) { |
| // UserTiming already stores lists of marks and measures by name, so |
| // requesting them directly is much more efficient than getting the full |
| // lists of marks and measures and then filtering during the merge. |
| entries = MergePerformanceEntryVectors( |
| entries, user_timing_->GetMarks(maybe_name), g_null_atom); |
| entries = MergePerformanceEntryVectors( |
| entries, user_timing_->GetMeasures(maybe_name), g_null_atom); |
| } else { |
| entries = MergePerformanceEntryVectors(entries, user_timing_->GetMarks(), |
| g_null_atom); |
| entries = MergePerformanceEntryVectors( |
| entries, user_timing_->GetMeasures(), g_null_atom); |
| } |
| } |
| |
| if (paint_entries_timing_.size()) { |
| entries = MergePerformanceEntryVectors(entries, paint_entries_timing_, |
| maybe_name); |
| } |
| |
| if (RuntimeEnabledFeatures::NavigationIdEnabled(GetExecutionContext())) { |
| entries = MergePerformanceEntryVectors( |
| entries, back_forward_cache_restoration_buffer_, maybe_name); |
| } |
| |
| if (RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled( |
| GetExecutionContext()) && |
| soft_navigation_buffer_.size()) { |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kSoftNavigationHeuristics); |
| entries = MergePerformanceEntryVectors(entries, soft_navigation_buffer_, |
| maybe_name); |
| } |
| |
| if (RuntimeEnabledFeatures::LongAnimationFrameTimingEnabled( |
| GetExecutionContext()) && |
| long_animation_frame_buffer_.size()) { |
| entries = MergePerformanceEntryVectors( |
| entries, long_animation_frame_buffer_, maybe_name); |
| } |
| |
| if (visibility_state_buffer_.size()) { |
| entries = MergePerformanceEntryVectors(entries, visibility_state_buffer_, |
| maybe_name); |
| } |
| |
| return entries; |
| } |
| |
| PerformanceEntryVector Performance::getBufferedEntriesByType( |
| const AtomicString& entry_type, |
| bool include_soft_navigation_observations) { |
| PerformanceEntry::EntryType type = |
| PerformanceEntry::ToEntryTypeEnum(entry_type); |
| return getEntriesByTypeInternal(type, /*maybe_name=*/g_null_atom, |
| include_soft_navigation_observations); |
| } |
| |
| PerformanceEntryVector Performance::getEntriesByType( |
| const AtomicString& entry_type) { |
| return GetEntriesByTypeForCurrentFrame(entry_type); |
| } |
| |
| PerformanceEntryVector Performance::GetEntriesByTypeForCurrentFrame( |
| const AtomicString& entry_type, |
| const AtomicString& maybe_name) { |
| PerformanceEntry::EntryType type = |
| PerformanceEntry::ToEntryTypeEnum(entry_type); |
| if (!PerformanceEntry::IsValidTimelineEntryType(type)) { |
| PerformanceEntryVector empty_entries; |
| if (ExecutionContext* execution_context = GetExecutionContext()) { |
| String message = "Deprecated API for given entry type."; |
| execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kJavaScript, |
| mojom::ConsoleMessageLevel::kWarning, message)); |
| } |
| return empty_entries; |
| } |
| return getEntriesByTypeInternal(type, maybe_name); |
| } |
| |
| PerformanceEntryVector Performance::getEntriesByTypeInternal( |
| PerformanceEntry::EntryType type, |
| const AtomicString& maybe_name, |
| bool include_soft_navigation_observations) { |
| // This vector may be used by any cases below which require local storage. |
| // Cases which refer to pre-existing vectors may simply set `entries` instead. |
| PerformanceEntryVector entries_storage; |
| |
| PerformanceEntryVector* entries = &entries_storage; |
| bool already_filtered_by_name = false; |
| switch (type) { |
| case PerformanceEntry::kResource: |
| UseCounter::Count(GetExecutionContext(), WebFeature::kResourceTiming); |
| entries = &resource_timing_buffer_; |
| break; |
| |
| case PerformanceEntry::kElement: |
| entries = &element_timing_buffer_; |
| break; |
| |
| case PerformanceEntry::kEvent: |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kEventTimingExplicitlyRequested); |
| entries = &event_timing_buffer_; |
| break; |
| |
| case PerformanceEntry::kFirstInput: |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kEventTimingExplicitlyRequested); |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kEventTimingFirstInputExplicitlyRequested); |
| if (first_input_timing_) |
| entries_storage = {first_input_timing_}; |
| break; |
| |
| case PerformanceEntry::kNavigation: |
| UseCounter::Count(GetExecutionContext(), WebFeature::kNavigationTimingL2); |
| if (navigation_timing_) |
| entries_storage = {navigation_timing_}; |
| break; |
| |
| case PerformanceEntry::kMark: |
| if (user_timing_) { |
| if (maybe_name) { |
| entries_storage = user_timing_->GetMarks(maybe_name); |
| already_filtered_by_name = true; |
| } else { |
| entries_storage = user_timing_->GetMarks(); |
| } |
| } |
| break; |
| |
| case PerformanceEntry::kMeasure: |
| if (user_timing_) { |
| if (maybe_name) { |
| entries_storage = user_timing_->GetMeasures(maybe_name); |
| already_filtered_by_name = true; |
| } else { |
| entries_storage = user_timing_->GetMeasures(); |
| } |
| } |
| break; |
| |
| case PerformanceEntry::kPaint: { |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kPaintTimingRequested); |
| |
| FilterEntriesTriggeredBySoftNavigationIfNeeded( |
| paint_entries_timing_, &entries, |
| include_soft_navigation_observations); |
| break; |
| } |
| |
| case PerformanceEntry::kLongTask: |
| entries = &longtask_buffer_; |
| break; |
| |
| // TaskAttribution & script entries are only associated to longtask entries. |
| case PerformanceEntry::kTaskAttribution: |
| case PerformanceEntry::kScript: |
| break; |
| |
| case PerformanceEntry::kLayoutShift: |
| entries = &layout_shift_buffer_; |
| break; |
| |
| case PerformanceEntry::kLargestContentfulPaint: |
| FilterEntriesTriggeredBySoftNavigationIfNeeded( |
| largest_contentful_paint_buffer_, &entries, |
| include_soft_navigation_observations); |
| break; |
| |
| case PerformanceEntry::kVisibilityState: |
| entries = &visibility_state_buffer_; |
| break; |
| |
| case PerformanceEntry::kBackForwardCacheRestoration: |
| if (RuntimeEnabledFeatures::NavigationIdEnabled(GetExecutionContext())) |
| entries = &back_forward_cache_restoration_buffer_; |
| break; |
| |
| case PerformanceEntry::kSoftNavigation: |
| if (RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled( |
| GetExecutionContext())) { |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kSoftNavigationHeuristics); |
| entries = &soft_navigation_buffer_; |
| } |
| break; |
| |
| case PerformanceEntry::kLongAnimationFrame: |
| if (RuntimeEnabledFeatures::LongAnimationFrameTimingEnabled( |
| GetExecutionContext())) { |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kLongAnimationFrameRequested); |
| entries = &long_animation_frame_buffer_; |
| } |
| break; |
| |
| case PerformanceEntry::kInvalid: |
| break; |
| } |
| |
| DCHECK_NE(entries, nullptr); |
| if (!maybe_name || already_filtered_by_name) { |
| return *entries; |
| } |
| |
| PerformanceEntryVector filtered_entries; |
| std::copy_if(entries->begin(), entries->end(), |
| std::back_inserter(filtered_entries), |
| [&](const PerformanceEntry* entry) { |
| return entry->name() == maybe_name; |
| }); |
| return filtered_entries; |
| } |
| |
| PerformanceEntryVector Performance::getEntriesByName( |
| const AtomicString& name, |
| const AtomicString& entry_type) { |
| PerformanceEntryVector entries; |
| |
| // Get sorted entry list based on provided input. |
| if (entry_type.IsNull()) { |
| entries = GetEntriesForCurrentFrame(name); |
| } else { |
| entries = GetEntriesByTypeForCurrentFrame(entry_type, name); |
| } |
| |
| return entries; |
| } |
| |
| PerformanceEntryVector Performance::GetEntriesWithChildFrames( |
| ScriptState* script_state, |
| const AtomicString& maybe_type, |
| const AtomicString& maybe_name) { |
| PerformanceEntryVector entries; |
| |
| LocalDOMWindow* window = LocalDOMWindow::From(script_state); |
| if (!window) { |
| return entries; |
| } |
| LocalFrame* root_frame = window->GetFrame(); |
| if (!root_frame) { |
| return entries; |
| } |
| const SecurityOrigin* root_origin = window->GetSecurityOrigin(); |
| |
| HeapDeque<Member<Frame>> queue; |
| queue.push_back(root_frame); |
| |
| while (!queue.empty()) { |
| Frame* current_frame = queue.TakeFirst(); |
| |
| if (LocalFrame* local_frame = DynamicTo<LocalFrame>(current_frame)) { |
| // Get the Performance object from the current frame. |
| LocalDOMWindow* current_window = local_frame->DomWindow(); |
| // As we verified that the frame this was called with is not detached when |
| // entring this loop, we can assume that all its children are also not |
| // detached, and hence have a window object. |
| DCHECK(current_window); |
| |
| // Validate that the child frame's origin is the same as the root |
| // frame. |
| const SecurityOrigin* current_origin = |
| current_window->GetSecurityOrigin(); |
| if (root_origin->IsSameOriginWith(current_origin)) { |
| WindowPerformance* window_performance = |
| DOMWindowPerformance::performance(*current_window); |
| |
| // Get the performance entries based on maybe_type input. Since the root |
| // frame can script the current frame, its okay to expose the current |
| // frame's performance entries to the root. |
| PerformanceEntryVector current_entries; |
| if (!maybe_type) { |
| current_entries = |
| window_performance->GetEntriesForCurrentFrame(maybe_name); |
| } else { |
| current_entries = window_performance->GetEntriesByTypeForCurrentFrame( |
| maybe_type, maybe_name); |
| } |
| |
| entries.AppendVector(current_entries); |
| } |
| } |
| |
| // Add both Local and Remote Frame children to the queue. |
| for (Frame* child = current_frame->FirstChild(); child; |
| child = child->NextSibling()) { |
| queue.push_back(child); |
| } |
| } |
| |
| std::sort(entries.begin(), entries.end(), |
| PerformanceEntry::StartTimeCompareLessThan); |
| |
| return entries; |
| } |
| |
| void Performance::clearResourceTimings() { |
| resource_timing_buffer_.clear(); |
| } |
| |
| void Performance::setResourceTimingBufferSize(unsigned size) { |
| resource_timing_buffer_size_limit_ = size; |
| } |
| |
| void Performance::setBackForwardCacheRestorationBufferSizeForTest( |
| unsigned size) { |
| back_forward_cache_restoration_buffer_size_limit_ = size; |
| } |
| |
| void Performance::AddResourceTiming(mojom::blink::ResourceTimingInfoPtr info, |
| const AtomicString& initiator_type) { |
| ExecutionContext* context = GetExecutionContext(); |
| auto* entry = MakeGarbageCollected<PerformanceResourceTiming>( |
| std::move(info), initiator_type, time_origin_, |
| cross_origin_isolated_capability_, context); |
| NotifyObserversOfEntry(*entry); |
| // https://w3c.github.io/resource-timing/#dfn-add-a-performanceresourcetiming-entry |
| if (CanAddResourceTimingEntry() && |
| !resource_timing_buffer_full_event_pending_) { |
| InsertEntryIntoSortedBuffer(resource_timing_buffer_, *entry, kRecordSwaps); |
| return; |
| } |
| |
| // The Resource Timing entries have a special processing model in which there |
| // is a secondary buffer but getting those entries requires handling the |
| // buffer full event, and the PerformanceObserver with buffered flag only |
| // receives the entries from the primary buffer, so it's ok to increase |
| // the dropped entries count here. |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kResource)->value); |
| if (!resource_timing_buffer_full_event_pending_) { |
| resource_timing_buffer_full_event_pending_ = true; |
| resource_timing_buffer_full_timer_.StartOneShot(base::TimeDelta(), |
| FROM_HERE); |
| } |
| resource_timing_secondary_buffer_.push_back(entry); |
| } |
| |
| // Called after loadEventEnd happens. |
| void Performance::NotifyNavigationTimingToObservers() { |
| if (navigation_timing_) |
| NotifyObserversOfEntry(*navigation_timing_); |
| } |
| |
| bool Performance::IsElementTimingBufferFull() const { |
| return element_timing_buffer_.size() >= element_timing_buffer_max_size_; |
| } |
| |
| bool Performance::IsEventTimingBufferFull() const { |
| return event_timing_buffer_.size() >= event_timing_buffer_max_size_; |
| } |
| |
| bool Performance::IsLongAnimationFrameBufferFull() const { |
| return long_animation_frame_buffer_.size() >= |
| kDefaultLongAnimationFrameBufferSize; |
| } |
| |
| void Performance::CopySecondaryBuffer() { |
| // https://w3c.github.io/resource-timing/#dfn-copy-secondary-buffer |
| while (!resource_timing_secondary_buffer_.empty() && |
| 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_.empty()) { |
| 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::AddElementTimingBuffer(PerformanceElementTiming& entry) { |
| if (!IsElementTimingBufferFull()) { |
| InsertEntryIntoSortedBuffer(element_timing_buffer_, entry, kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kElement)->value); |
| } |
| } |
| |
| void Performance::AddEventTimingBuffer(PerformanceEventTiming& entry) { |
| if (!IsEventTimingBufferFull()) { |
| InsertEntryIntoSortedBuffer(event_timing_buffer_, entry, kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kEvent)->value); |
| } |
| } |
| |
| void Performance::AddLayoutShiftBuffer(LayoutShift& entry) { |
| probe::PerformanceEntryAdded(GetExecutionContext(), &entry); |
| if (layout_shift_buffer_.size() < kDefaultLayoutShiftBufferSize) { |
| InsertEntryIntoSortedBuffer(layout_shift_buffer_, entry, kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kLayoutShift)->value); |
| } |
| } |
| |
| void Performance::AddLargestContentfulPaint(LargestContentfulPaint* entry) { |
| probe::PerformanceEntryAdded(GetExecutionContext(), entry); |
| if (largest_contentful_paint_buffer_.size() < |
| kDefaultLargestContenfulPaintSize) { |
| InsertEntryIntoSortedBuffer(largest_contentful_paint_buffer_, *entry, |
| kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_ |
| .find(PerformanceEntry::kLargestContentfulPaint) |
| ->value); |
| } |
| } |
| |
| void Performance::AddSoftNavigationToPerformanceTimeline( |
| SoftNavigationEntry* entry) { |
| probe::PerformanceEntryAdded(GetExecutionContext(), entry); |
| if (soft_navigation_buffer_.size() < kDefaultSoftNavigationBufferSize) { |
| InsertEntryIntoSortedBuffer(soft_navigation_buffer_, *entry, kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kSoftNavigation) |
| ->value); |
| } |
| } |
| |
| void Performance::AddFirstPaintTiming(base::TimeTicks start_time, |
| bool is_triggered_by_soft_navigation) { |
| AddPaintTiming(PerformancePaintTiming::PaintType::kFirstPaint, start_time, |
| is_triggered_by_soft_navigation); |
| } |
| |
| void Performance::AddFirstContentfulPaintTiming( |
| base::TimeTicks start_time, |
| bool is_triggered_by_soft_navigation) { |
| AddPaintTiming(PerformancePaintTiming::PaintType::kFirstContentfulPaint, |
| start_time, is_triggered_by_soft_navigation); |
| } |
| |
| void Performance::AddPaintTiming(PerformancePaintTiming::PaintType type, |
| base::TimeTicks start_time, |
| bool is_triggered_by_soft_navigation) { |
| PerformanceEntry* entry = MakeGarbageCollected<PerformancePaintTiming>( |
| type, MonotonicTimeToDOMHighResTimeStamp(start_time), |
| DynamicTo<LocalDOMWindow>(GetExecutionContext()), |
| is_triggered_by_soft_navigation); |
| DCHECK((type == PerformancePaintTiming::PaintType::kFirstPaint) || |
| (type == PerformancePaintTiming::PaintType::kFirstContentfulPaint)); |
| if (paint_entries_timing_.size() < kDefaultPaintEntriesBufferSize) { |
| InsertEntryIntoSortedBuffer(paint_entries_timing_, *entry, kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kPaint)->value); |
| } |
| NotifyObserversOfEntry(*entry); |
| } |
| |
| bool Performance::CanAddResourceTimingEntry() { |
| // https://w3c.github.io/resource-timing/#dfn-can-add-resource-timing-entry |
| return resource_timing_buffer_.size() < resource_timing_buffer_size_limit_; |
| } |
| |
| void Performance::AddLongTaskTiming(base::TimeTicks start_time, |
| base::TimeTicks end_time, |
| const AtomicString& name, |
| const AtomicString& container_type, |
| const AtomicString& container_src, |
| const AtomicString& container_id, |
| const AtomicString& container_name) { |
| double dom_high_res_start_time = |
| MonotonicTimeToDOMHighResTimeStamp(start_time); |
| |
| ExecutionContext* execution_context = GetExecutionContext(); |
| auto* entry = MakeGarbageCollected<PerformanceLongTaskTiming>( |
| dom_high_res_start_time, |
| // Convert the delta between start and end times to an int to reduce the |
| // granularity of the duration to 1 ms. |
| static_cast<int>(MonotonicTimeToDOMHighResTimeStamp(end_time) - |
| dom_high_res_start_time), |
| name, container_type, container_src, container_id, container_name, |
| DynamicTo<LocalDOMWindow>(execution_context)); |
| if (longtask_buffer_.size() < kDefaultLongTaskBufferSize) { |
| InsertEntryIntoSortedBuffer(longtask_buffer_, *entry, kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_.find(PerformanceEntry::kLongTask)->value); |
| UseCounter::Count(execution_context, WebFeature::kLongTaskBufferFull); |
| } |
| if ((++long_task_counter_ % kLongTaskUkmSampleInterval) == 0) { |
| RecordLongTaskUkm(execution_context, |
| base::Milliseconds(dom_high_res_start_time), |
| end_time - start_time); |
| } |
| NotifyObserversOfEntry(*entry); |
| } |
| |
| void Performance::AddBackForwardCacheRestoration( |
| base::TimeTicks start_time, |
| base::TimeTicks pageshow_start_time, |
| base::TimeTicks pageshow_end_time) { |
| auto* entry = MakeGarbageCollected<BackForwardCacheRestoration>( |
| MonotonicTimeToDOMHighResTimeStamp(start_time), |
| MonotonicTimeToDOMHighResTimeStamp(pageshow_start_time), |
| MonotonicTimeToDOMHighResTimeStamp(pageshow_end_time), |
| DynamicTo<LocalDOMWindow>(GetExecutionContext())); |
| if (back_forward_cache_restoration_buffer_.size() < |
| back_forward_cache_restoration_buffer_size_limit_) { |
| InsertEntryIntoSortedBuffer(back_forward_cache_restoration_buffer_, *entry, |
| kRecordSwaps); |
| } else { |
| ++(dropped_entries_count_map_ |
| .find(PerformanceEntry::kBackForwardCacheRestoration) |
| ->value); |
| } |
| NotifyObserversOfEntry(*entry); |
| } |
| |
| UserTiming& Performance::GetUserTiming() { |
| if (!user_timing_) |
| user_timing_ = MakeGarbageCollected<UserTiming>(*this); |
| return *user_timing_; |
| } |
| |
| PerformanceMark* Performance::mark(ScriptState* script_state, |
| const AtomicString& mark_name, |
| PerformanceMarkOptions* mark_options, |
| ExceptionState& exception_state) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(const AtomicString, mark_fully_loaded, |
| ("mark_fully_loaded")); |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(const AtomicString, mark_fully_visible, |
| ("mark_fully_visible")); |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(const AtomicString, mark_interactive, |
| ("mark_interactive")); |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(const AtomicString, mark_feature_usage, |
| ("mark_feature_usage")); |
| if (mark_options && |
| (mark_options->hasStartTime() || mark_options->hasDetail())) { |
| UseCounter::Count(GetExecutionContext(), WebFeature::kUserTimingL3); |
| } |
| PerformanceMark* performance_mark = PerformanceMark::Create( |
| script_state, mark_name, mark_options, exception_state); |
| if (performance_mark) { |
| background_tracing_helper_->MaybeEmitBackgroundTracingPerformanceMarkEvent( |
| *performance_mark); |
| GetUserTiming().AddMarkToPerformanceTimeline(*performance_mark, |
| mark_options); |
| if (mark_name == mark_fully_loaded) { |
| if (LocalDOMWindow* window = LocalDOMWindow::From(script_state)) { |
| window->GetFrame() |
| ->Loader() |
| .GetDocumentLoader() |
| ->GetTiming() |
| .SetUserTimingMarkFullyLoaded( |
| base::Milliseconds(performance_mark->startTime())); |
| } |
| } else if (mark_name == mark_fully_visible) { |
| if (LocalDOMWindow* window = LocalDOMWindow::From(script_state)) { |
| window->GetFrame() |
| ->Loader() |
| .GetDocumentLoader() |
| ->GetTiming() |
| .SetUserTimingMarkFullyVisible( |
| base::Milliseconds(performance_mark->startTime())); |
| } |
| } else if (mark_name == mark_interactive) { |
| if (LocalDOMWindow* window = LocalDOMWindow::From(script_state)) { |
| window->GetFrame() |
| ->Loader() |
| .GetDocumentLoader() |
| ->GetTiming() |
| .SetUserTimingMarkInteractive( |
| base::Milliseconds(performance_mark->startTime())); |
| } |
| } else if (mark_name == mark_feature_usage && mark_options->hasDetail()) { |
| if (RuntimeEnabledFeatures::PerformanceMarkFeatureUsageEnabled()) { |
| ProcessUserFeatureMark(mark_options); |
| } |
| } |
| NotifyObserversOfEntry(*performance_mark); |
| } |
| return performance_mark; |
| } |
| |
| void Performance::ProcessUserFeatureMark( |
| const PerformanceMarkOptions* mark_options) { |
| const ExecutionContext* exec_context = GetExecutionContext(); |
| if (!exec_context) { |
| return; |
| } |
| |
| const ScriptValue& detail = mark_options->detail(); |
| if (!detail.IsObject()) { |
| return; |
| } |
| |
| v8::Isolate* isolate = GetExecutionContext()->GetIsolate(); |
| v8::Local<v8::Context> current_context = isolate->GetCurrentContext(); |
| v8::Local<v8::Object> object; |
| if (!detail.V8Value()->ToObject(current_context).ToLocal(&object)) { |
| return; |
| } |
| |
| v8::Local<v8::Value> user_feature_name_val; |
| if (!object->Get(current_context, V8AtomicString(isolate, "feature")) |
| .ToLocal(&user_feature_name_val) || |
| user_feature_name_val->IsUndefined()) { |
| return; |
| } |
| |
| v8::Local<v8::String> user_feature_name; |
| if (!user_feature_name_val->ToString(current_context) |
| .ToLocal(&user_feature_name)) { |
| return; |
| } |
| |
| String blink_user_feature_name = |
| ToBlinkString<String>(isolate, user_feature_name, kDoNotExternalize); |
| |
| // Check if the user feature name is mapped to an allowed WebFeature. |
| auto maybe_web_feature = |
| PerformanceMark::GetWebFeatureForUserFeatureName(blink_user_feature_name); |
| if (!maybe_web_feature.has_value()) { |
| // We have no matching WebFeature translation yet, skip. |
| return; |
| } |
| |
| // Tick the corresponding use counter. |
| UseCounter::Count(GetExecutionContext(), maybe_web_feature.value()); |
| } |
| |
| void Performance::clearMarks(const AtomicString& mark_name) { |
| GetUserTiming().ClearMarks(mark_name); |
| } |
| |
| PerformanceMeasure* Performance::measure(ScriptState* script_state, |
| const AtomicString& measure_name, |
| ExceptionState& exception_state) { |
| // When |startOrOptions| is not provided, it's assumed to be an empty |
| // dictionary. |
| return MeasureInternal(script_state, measure_name, nullptr, std::nullopt, |
| exception_state); |
| } |
| |
| PerformanceMeasure* Performance::measure( |
| ScriptState* script_state, |
| const AtomicString& measure_name, |
| const V8UnionPerformanceMeasureOptionsOrString* start_or_options, |
| ExceptionState& exception_state) { |
| return MeasureInternal(script_state, measure_name, start_or_options, |
| std::nullopt, exception_state); |
| } |
| |
| PerformanceMeasure* Performance::measure( |
| ScriptState* script_state, |
| const AtomicString& measure_name, |
| const V8UnionPerformanceMeasureOptionsOrString* start_or_options, |
| const String& end, |
| ExceptionState& exception_state) { |
| return MeasureInternal(script_state, measure_name, start_or_options, |
| std::optional<String>(end), exception_state); |
| } |
| |
| // |MeasureInternal| exists to unify the arguments from different |
| // `performance.measure()` overloads into a consistent form, then delegate to |
| // |MeasureWithDetail|. |
| // |
| // |start_or_options| is either a String or a dictionary of options. When it's |
| // a String, it represents a starting performance mark. When it's a dictionary, |
| // the allowed fields are 'start', 'duration', 'end' and 'detail'. However, |
| // there are some combinations of fields and parameters which must raise |
| // errors. Specifically, the spec (https://https://w3c.github.io/user-timing/) |
| // requires errors to thrown in the following cases: |
| // - If |start_or_options| is a dictionary and 'end_mark' is passed. |
| // - If an options dictionary contains neither a 'start' nor an 'end' field. |
| // - If an options dictionary contains all of 'start', 'duration' and 'end'. |
| // |
| // |end_mark| will be std::nullopt unless the `performance.measure()` overload |
| // specified an end mark. |
| PerformanceMeasure* Performance::MeasureInternal( |
| ScriptState* script_state, |
| const AtomicString& measure_name, |
| const V8UnionPerformanceMeasureOptionsOrString* start_or_options, |
| std::optional<String> end_mark, |
| ExceptionState& exception_state) { |
| // An empty option is treated with no difference as null, undefined. |
| if (start_or_options && start_or_options->IsPerformanceMeasureOptions() && |
| !IsMeasureOptionsEmpty( |
| *start_or_options->GetAsPerformanceMeasureOptions())) { |
| UseCounter::Count(GetExecutionContext(), WebFeature::kUserTimingL3); |
| // measure("name", { start, end }, *) |
| if (end_mark) { |
| exception_state.ThrowTypeError( |
| "If a non-empty PerformanceMeasureOptions object was passed, " |
| "|end_mark| must not be passed."); |
| return nullptr; |
| } |
| const PerformanceMeasureOptions* options = |
| start_or_options->GetAsPerformanceMeasureOptions(); |
| if (!options->hasStart() && !options->hasEnd()) { |
| exception_state.ThrowTypeError( |
| "If a non-empty PerformanceMeasureOptions object was passed, at " |
| "least one of its 'start' or 'end' properties must be present."); |
| return nullptr; |
| } |
| |
| if (options->hasStart() && options->hasDuration() && options->hasEnd()) { |
| exception_state.ThrowTypeError( |
| "If a non-empty PerformanceMeasureOptions object was passed, it " |
| "must not have all of its 'start', 'duration', and 'end' " |
| "properties defined"); |
| return nullptr; |
| } |
| |
| V8UnionDoubleOrString* start = options->getStartOr(nullptr); |
| std::optional<double> duration; |
| if (options->hasDuration()) { |
| duration = options->duration(); |
| } |
| V8UnionDoubleOrString* end = options->getEndOr(nullptr); |
| |
| return MeasureWithDetail( |
| script_state, measure_name, start, duration, end, |
| options->hasDetail() ? options->detail() : ScriptValue(), |
| exception_state); |
| } |
| |
| // measure("name", "mark1", *) |
| V8UnionDoubleOrString* start = nullptr; |
| if (start_or_options && start_or_options->IsString()) { |
| start = MakeGarbageCollected<V8UnionDoubleOrString>( |
| start_or_options->GetAsString()); |
| } |
| // We let |end_mark| behave the same whether it's empty, undefined or null |
| // in JS, as long as |end_mark| is null in C++. |
| V8UnionDoubleOrString* end = nullptr; |
| if (end_mark) { |
| end = MakeGarbageCollected<V8UnionDoubleOrString>(*end_mark); |
| } |
| return MeasureWithDetail(script_state, measure_name, start, |
| /* duration = */ std::nullopt, end, |
| ScriptValue::CreateNull(script_state->GetIsolate()), |
| exception_state); |
| } |
| |
| PerformanceMeasure* Performance::MeasureWithDetail( |
| ScriptState* script_state, |
| const AtomicString& measure_name, |
| const V8UnionDoubleOrString* start, |
| const std::optional<double>& duration, |
| const V8UnionDoubleOrString* end, |
| const ScriptValue& detail, |
| ExceptionState& exception_state) { |
| PerformanceMeasure* performance_measure = GetUserTiming().Measure( |
| script_state, measure_name, start, duration, end, detail, exception_state, |
| LocalDOMWindow::From(script_state)); |
| if (performance_measure) |
| NotifyObserversOfEntry(*performance_measure); |
| return performance_measure; |
| } |
| |
| void Performance::clearMeasures(const AtomicString& measure_name) { |
| GetUserTiming().ClearMeasures(measure_name); |
| } |
| |
| void Performance::RegisterPerformanceObserver(PerformanceObserver& observer) { |
| observer_filter_options_ |= observer.FilterOptions(); |
| observers_.insert(&observer); |
| } |
| |
| void Performance::UnregisterPerformanceObserver( |
| PerformanceObserver& old_observer) { |
| observers_.erase(&old_observer); |
| UpdatePerformanceObserverFilterOptions(); |
| } |
| |
| void Performance::UpdatePerformanceObserverFilterOptions() { |
| observer_filter_options_ = PerformanceEntry::kInvalid; |
| for (const auto& observer : observers_) { |
| observer_filter_options_ |= observer->FilterOptions(); |
| } |
| } |
| |
| void Performance::NotifyObserversOfEntry(PerformanceEntry& entry) const { |
| bool observer_found = false; |
| for (auto& observer : observers_) { |
| if (observer->FilterOptions() & entry.EntryTypeEnum() && |
| (!entry.IsTriggeredBySoftNavigation() || |
| observer->IncludeSoftNavigationObservations()) && |
| observer->CanObserve(entry)) { |
| observer->EnqueuePerformanceEntry(entry); |
| observer_found = true; |
| } |
| } |
| if (observer_found && entry.EntryTypeEnum() == PerformanceEntry::kPaint) |
| UseCounter::Count(GetExecutionContext(), WebFeature::kPaintTimingObserved); |
| } |
| |
| bool Performance::HasObserverFor( |
| PerformanceEntry::EntryType filter_type) const { |
| return observer_filter_options_ & filter_type; |
| } |
| |
| void Performance::ActivateObserver(PerformanceObserver& observer) { |
| if (active_observers_.empty()) |
| deliver_observations_timer_.StartOneShot(base::TimeDelta(), FROM_HERE); |
| |
| if (suspended_observers_.Contains(&observer)) |
| suspended_observers_.erase(&observer); |
| active_observers_.insert(&observer); |
| } |
| |
| void Performance::SuspendObserver(PerformanceObserver& observer) { |
| DCHECK(!suspended_observers_.Contains(&observer)); |
| if (!active_observers_.Contains(&observer)) |
| return; |
| active_observers_.erase(&observer); |
| suspended_observers_.insert(&observer); |
| } |
| |
| void Performance::DeliverObservationsTimerFired(TimerBase*) { |
| decltype(active_observers_) observers; |
| active_observers_.Swap(observers); |
| for (const auto& observer : observers) { |
| observer->Deliver(observer->RequiresDroppedEntries() |
| ? std::optional<int>(GetDroppedEntriesForTypes( |
| observer->FilterOptions())) |
| : std::nullopt); |
| } |
| } |
| |
| int Performance::GetDroppedEntriesForTypes(PerformanceEntryTypeMask types) { |
| int dropped_count = 0; |
| for (const auto type : kDroppableEntryTypes) { |
| if (types & type) |
| dropped_count += dropped_entries_count_map_.at(type); |
| } |
| return dropped_count; |
| } |
| |
| // static |
| DOMHighResTimeStamp Performance::ClampTimeResolution( |
| base::TimeDelta time, |
| bool cross_origin_isolated_capability) { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(TimeClamper, clamper, ()); |
| return clamper.ClampTimeResolution(time, cross_origin_isolated_capability) |
| .InMillisecondsF(); |
| } |
| |
| // static |
| DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp( |
| base::TimeTicks time_origin, |
| base::TimeTicks monotonic_time, |
| bool allow_negative_value, |
| bool cross_origin_isolated_capability) { |
| // Avoid exposing raw platform timestamps. |
| if (monotonic_time.is_null() || time_origin.is_null()) |
| return 0.0; |
| |
| DOMHighResTimeStamp clamped_time = |
| ClampTimeResolution(monotonic_time.since_origin(), |
| cross_origin_isolated_capability) - |
| ClampTimeResolution(time_origin.since_origin(), |
| cross_origin_isolated_capability); |
| if (clamped_time < 0 && !allow_negative_value) |
| return 0.0; |
| return clamped_time; |
| } |
| |
| DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp( |
| base::TimeTicks monotonic_time) const { |
| return MonotonicTimeToDOMHighResTimeStamp(time_origin_, monotonic_time, |
| false /* allow_negative_value */, |
| cross_origin_isolated_capability_); |
| } |
| |
| DOMHighResTimeStamp Performance::now() const { |
| return MonotonicTimeToDOMHighResTimeStamp(tick_clock_->NowTicks()); |
| } |
| |
| // static |
| bool Performance::CanExposeNode(Node* node) { |
| if (!node || !node->isConnected() || node->IsInShadowTree()) |
| return false; |
| |
| // Do not expose |node| when the document is not 'fully active'. |
| const Document& document = node->GetDocument(); |
| if (!document.IsActive() || !document.GetFrame()) |
| return false; |
| |
| return true; |
| } |
| |
| ScriptValue Performance::toJSONForBinding(ScriptState* script_state) const { |
| V8ObjectBuilder result(script_state); |
| BuildJSONValue(result); |
| return result.GetScriptValue(); |
| } |
| |
| void Performance::BuildJSONValue(V8ObjectBuilder& builder) const { |
| builder.AddNumber("timeOrigin", timeOrigin()); |
| // |memory| is not part of the spec, omitted. |
| } |
| |
| // Insert entry in PerformanceEntryVector while maintaining sorted order (via |
| // Bubble Sort). We assume that the order of insertion roughly corresponds to |
| // the order of the StartTime, hence the sort beginning from the tail-end. |
| void Performance::InsertEntryIntoSortedBuffer(PerformanceEntryVector& entries, |
| PerformanceEntry& entry, |
| Metrics record) { |
| entries.push_back(&entry); |
| |
| int number_of_swaps = 0; |
| |
| if (entries.size() > 1) { |
| // Bubble Sort from tail. |
| int left = entries.size() - 2; |
| while (left >= 0 && |
| entries[left]->startTime() > entries[left + 1]->startTime()) { |
| if (record == kRecordSwaps) { |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kPerformanceEntryBufferSwaps); |
| } |
| number_of_swaps++; |
| SwapEntries(entries, left, left + 1); |
| left--; |
| } |
| } |
| |
| UMA_HISTOGRAM_COUNTS_1000(kSwapsPerInsertionHistogram, number_of_swaps); |
| |
| return; |
| } |
| |
| void Performance::Trace(Visitor* visitor) const { |
| visitor->Trace(resource_timing_buffer_); |
| visitor->Trace(resource_timing_secondary_buffer_); |
| visitor->Trace(element_timing_buffer_); |
| visitor->Trace(event_timing_buffer_); |
| visitor->Trace(layout_shift_buffer_); |
| visitor->Trace(largest_contentful_paint_buffer_); |
| visitor->Trace(longtask_buffer_); |
| visitor->Trace(visibility_state_buffer_); |
| visitor->Trace(back_forward_cache_restoration_buffer_); |
| visitor->Trace(soft_navigation_buffer_); |
| visitor->Trace(long_animation_frame_buffer_); |
| visitor->Trace(navigation_timing_); |
| visitor->Trace(user_timing_); |
| visitor->Trace(paint_entries_timing_); |
| visitor->Trace(first_input_timing_); |
| visitor->Trace(observers_); |
| visitor->Trace(active_observers_); |
| visitor->Trace(suspended_observers_); |
| visitor->Trace(deliver_observations_timer_); |
| visitor->Trace(resource_timing_buffer_full_timer_); |
| visitor->Trace(background_tracing_helper_); |
| EventTarget::Trace(visitor); |
| } |
| |
| void Performance::SetClocksForTesting(const base::Clock* clock, |
| const base::TickClock* tick_clock) { |
| tick_clock_ = tick_clock; |
| // Recompute |unix_at_zero_monotonic_|. |
| unix_at_zero_monotonic_ = GetUnixAtZeroMonotonic(clock, tick_clock_); |
| } |
| |
| void Performance::ResetTimeOriginForTesting(base::TimeTicks time_origin) { |
| time_origin_ = time_origin; |
| } |
| |
| // TODO(https://crbug.com/1457049): remove this once visited links are |
| // partitioned. |
| bool Performance::softNavPaintMetricsSupported() const { |
| CHECK( |
| RuntimeEnabledFeatures::SoftNavigationHeuristicsExposeFPAndFCPEnabled()); |
| return true; |
| } |
| |
| } // namespace blink |