1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2012 Intel Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "third_party/blink/renderer/core/timing/performance.h"
33 
34 #include <algorithm>
35 
36 #include "base/metrics/histogram_macros.h"
37 #include "base/time/default_clock.h"
38 #include "base/time/default_tick_clock.h"
39 #include "third_party/blink/public/mojom/timing/worker_timing_container.mojom-blink-forward.h"
40 #include "third_party/blink/public/platform/platform.h"
41 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
42 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
43 #include "third_party/blink/renderer/bindings/core/v8/string_or_performance_measure_options.h"
44 #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
45 #include "third_party/blink/renderer/bindings/core/v8/v8_performance_mark_options.h"
46 #include "third_party/blink/renderer/bindings/core/v8/v8_performance_measure_options.h"
47 #include "third_party/blink/renderer/bindings/core/v8/v8_profiler_init_options.h"
48 #include "third_party/blink/renderer/core/dom/document.h"
49 #include "third_party/blink/renderer/core/dom/document_timing.h"
50 #include "third_party/blink/renderer/core/dom/dom_exception.h"
51 #include "third_party/blink/renderer/core/dom/events/event.h"
52 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
53 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
54 #include "third_party/blink/renderer/core/frame/local_frame.h"
55 #include "third_party/blink/renderer/core/inspector/console_message.h"
56 #include "third_party/blink/renderer/core/loader/document_load_timing.h"
57 #include "third_party/blink/renderer/core/loader/document_loader.h"
58 #include "third_party/blink/renderer/core/timing/largest_contentful_paint.h"
59 #include "third_party/blink/renderer/core/timing/layout_shift.h"
60 #include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h"
61 #include "third_party/blink/renderer/core/timing/performance_element_timing.h"
62 #include "third_party/blink/renderer/core/timing/performance_event_timing.h"
63 #include "third_party/blink/renderer/core/timing/performance_long_task_timing.h"
64 #include "third_party/blink/renderer/core/timing/performance_mark.h"
65 #include "third_party/blink/renderer/core/timing/performance_measure.h"
66 #include "third_party/blink/renderer/core/timing/performance_observer.h"
67 #include "third_party/blink/renderer/core/timing/performance_resource_timing.h"
68 #include "third_party/blink/renderer/core/timing/performance_user_timing.h"
69 #include "third_party/blink/renderer/core/timing/profiler.h"
70 #include "third_party/blink/renderer/core/timing/profiler_group.h"
71 #include "third_party/blink/renderer/core/timing/time_clamper.h"
72 #include "third_party/blink/renderer/platform/heap/heap.h"
73 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
74 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h"
75 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
76 #include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
77 #include "third_party/blink/renderer/platform/network/http_parsers.h"
78 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
79 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
80 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
81 
82 namespace blink {
83 
84 namespace {
85 
GetSecurityOrigin(ExecutionContext * context)86 const SecurityOrigin* GetSecurityOrigin(ExecutionContext* context) {
87   if (context)
88     return context->GetSecurityOrigin();
89   return nullptr;
90 }
91 
IsMeasureOptionsEmpty(const PerformanceMeasureOptions & options)92 bool IsMeasureOptionsEmpty(const PerformanceMeasureOptions& options) {
93   return !options.hasDetail() && !options.hasEnd() && !options.hasStart() &&
94          !options.hasDuration();
95 }
96 
97 }  // namespace
98 
99 using PerformanceObserverVector = HeapVector<Member<PerformanceObserver>>;
100 
101 constexpr size_t kDefaultResourceTimingBufferSize = 250;
102 constexpr size_t kDefaultEventTimingBufferSize = 150;
103 constexpr size_t kDefaultElementTimingBufferSize = 150;
104 constexpr size_t kDefaultLayoutShiftBufferSize = 150;
105 constexpr size_t kDefaultLargestContenfulPaintSize = 150;
106 constexpr size_t kDefaultLongTaskBufferSize = 200;
107 
Performance(base::TimeTicks time_origin,scoped_refptr<base::SingleThreadTaskRunner> task_runner)108 Performance::Performance(
109     base::TimeTicks time_origin,
110     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
111     : resource_timing_buffer_size_limit_(kDefaultResourceTimingBufferSize),
112       event_timing_buffer_max_size_(kDefaultEventTimingBufferSize),
113       element_timing_buffer_max_size_(kDefaultElementTimingBufferSize),
114       user_timing_(nullptr),
115       time_origin_(time_origin),
116       tick_clock_(base::DefaultTickClock::GetInstance()),
117       observer_filter_options_(PerformanceEntry::kInvalid),
118       task_runner_(std::move(task_runner)),
119       deliver_observations_timer_(task_runner_,
120                                   this,
121                                   &Performance::DeliverObservationsTimerFired),
122       resource_timing_buffer_full_timer_(
123           task_runner_,
124           this,
125           &Performance::FireResourceTimingBufferFull) {
126   unix_at_zero_monotonic_ = ConvertSecondsToDOMHighResTimeStamp(
127       base::DefaultClock::GetInstance()->Now().ToDoubleT() -
128       tick_clock_->NowTicks().since_origin().InSecondsF());
129 }
130 
131 Performance::~Performance() = default;
132 
InterfaceName() const133 const AtomicString& Performance::InterfaceName() const {
134   return event_target_names::kPerformance;
135 }
136 
timing() const137 PerformanceTiming* Performance::timing() const {
138   return nullptr;
139 }
140 
navigation() const141 PerformanceNavigation* Performance::navigation() const {
142   return nullptr;
143 }
144 
memory() const145 MemoryInfo* Performance::memory() const {
146   return nullptr;
147 }
148 
eventCounts()149 EventCounts* Performance::eventCounts() {
150   return nullptr;
151 }
152 
measureMemory(ScriptState * script_state,ExceptionState & exception_state) const153 ScriptPromise Performance::measureMemory(
154     ScriptState* script_state,
155     ExceptionState& exception_state) const {
156   return MeasureMemoryController::StartMeasurement(script_state,
157                                                    exception_state);
158 }
159 
timeOrigin() const160 DOMHighResTimeStamp Performance::timeOrigin() const {
161   DCHECK(!time_origin_.is_null());
162   return unix_at_zero_monotonic_ +
163          ConvertTimeTicksToDOMHighResTimeStamp(time_origin_);
164 }
165 
getEntries()166 PerformanceEntryVector Performance::getEntries() {
167   PerformanceEntryVector entries;
168 
169   entries.AppendVector(resource_timing_buffer_);
170   if (first_input_timing_)
171     entries.push_back(first_input_timing_);
172   if (!navigation_timing_)
173     navigation_timing_ = CreateNavigationTimingInstance();
174   // This extra checking is needed when WorkerPerformance
175   // calls this method.
176   if (navigation_timing_)
177     entries.push_back(navigation_timing_);
178 
179   if (user_timing_) {
180     entries.AppendVector(user_timing_->GetMarks());
181     entries.AppendVector(user_timing_->GetMeasures());
182   }
183 
184   if (first_paint_timing_)
185     entries.push_back(first_paint_timing_);
186   if (first_contentful_paint_timing_)
187     entries.push_back(first_contentful_paint_timing_);
188 
189   std::sort(entries.begin(), entries.end(),
190             PerformanceEntry::StartTimeCompareLessThan);
191   return entries;
192 }
193 
getBufferedEntriesByType(const AtomicString & entry_type)194 PerformanceEntryVector Performance::getBufferedEntriesByType(
195     const AtomicString& entry_type) {
196   PerformanceEntry::EntryType type =
197       PerformanceEntry::ToEntryTypeEnum(entry_type);
198   return getEntriesByTypeInternal(type);
199 }
200 
getEntriesByType(const AtomicString & entry_type)201 PerformanceEntryVector Performance::getEntriesByType(
202     const AtomicString& entry_type) {
203   PerformanceEntry::EntryType type =
204       PerformanceEntry::ToEntryTypeEnum(entry_type);
205   if (!PerformanceEntry::IsValidTimelineEntryType(type)) {
206     PerformanceEntryVector empty_entries;
207     String message = "Deprecated API for given entry type.";
208     GetExecutionContext()->AddConsoleMessage(
209         MakeGarbageCollected<ConsoleMessage>(
210             mojom::ConsoleMessageSource::kJavaScript,
211             mojom::ConsoleMessageLevel::kWarning, message));
212     return empty_entries;
213   }
214   return getEntriesByTypeInternal(type);
215 }
216 
getEntriesByTypeInternal(PerformanceEntry::EntryType type)217 PerformanceEntryVector Performance::getEntriesByTypeInternal(
218     PerformanceEntry::EntryType type) {
219   PerformanceEntryVector entries;
220   switch (type) {
221     case PerformanceEntry::kResource:
222       UseCounter::Count(GetExecutionContext(), WebFeature::kResourceTiming);
223       for (const auto& resource : resource_timing_buffer_)
224         entries.push_back(resource);
225       break;
226     case PerformanceEntry::kElement:
227       for (const auto& element : element_timing_buffer_)
228         entries.push_back(element);
229       break;
230     case PerformanceEntry::kEvent:
231       UseCounter::Count(GetExecutionContext(),
232                         WebFeature::kEventTimingExplicitlyRequested);
233       for (const auto& event : event_timing_buffer_)
234         entries.push_back(event);
235       break;
236     case PerformanceEntry::kFirstInput:
237       UseCounter::Count(GetExecutionContext(),
238                         WebFeature::kEventTimingExplicitlyRequested);
239       UseCounter::Count(GetExecutionContext(),
240                         WebFeature::kEventTimingFirstInputExplicitlyRequested);
241       if (first_input_timing_)
242         entries.push_back(first_input_timing_);
243       break;
244     case PerformanceEntry::kNavigation:
245       UseCounter::Count(GetExecutionContext(), WebFeature::kNavigationTimingL2);
246       if (!navigation_timing_)
247         navigation_timing_ = CreateNavigationTimingInstance();
248       if (navigation_timing_)
249         entries.push_back(navigation_timing_);
250       break;
251     case PerformanceEntry::kMark:
252       if (user_timing_)
253         entries.AppendVector(user_timing_->GetMarks());
254       break;
255     case PerformanceEntry::kMeasure:
256       if (user_timing_)
257         entries.AppendVector(user_timing_->GetMeasures());
258       break;
259     case PerformanceEntry::kPaint:
260       UseCounter::Count(GetExecutionContext(),
261                         WebFeature::kPaintTimingRequested);
262       if (first_paint_timing_)
263         entries.push_back(first_paint_timing_);
264       if (first_contentful_paint_timing_)
265         entries.push_back(first_contentful_paint_timing_);
266       break;
267     case PerformanceEntry::kLongTask:
268       for (const auto& entry : longtask_buffer_)
269         entries.push_back(entry);
270       break;
271     // TaskAttribution entries are only associated to longtask entries.
272     case PerformanceEntry::kTaskAttribution:
273       break;
274     case PerformanceEntry::kLayoutShift:
275       for (const auto& layout_shift : layout_shift_buffer_)
276         entries.push_back(layout_shift);
277       break;
278     case PerformanceEntry::kLargestContentfulPaint:
279       entries.AppendVector(largest_contentful_paint_buffer_);
280       break;
281     case PerformanceEntry::kVisibilityState:
282       entries.AppendVector(visibility_state_buffer_);
283       break;
284     case PerformanceEntry::kInvalid:
285       break;
286   }
287 
288   std::sort(entries.begin(), entries.end(),
289             PerformanceEntry::StartTimeCompareLessThan);
290   return entries;
291 }
292 
getEntriesByName(const AtomicString & name,const AtomicString & entry_type)293 PerformanceEntryVector Performance::getEntriesByName(
294     const AtomicString& name,
295     const AtomicString& entry_type) {
296   PerformanceEntryVector entries;
297   PerformanceEntry::EntryType type =
298       PerformanceEntry::ToEntryTypeEnum(entry_type);
299 
300   if (!entry_type.IsNull() &&
301       !PerformanceEntry::IsValidTimelineEntryType(type)) {
302     String message = "Deprecated API for given entry type.";
303     GetExecutionContext()->AddConsoleMessage(
304         MakeGarbageCollected<ConsoleMessage>(
305             mojom::ConsoleMessageSource::kJavaScript,
306             mojom::ConsoleMessageLevel::kWarning, message));
307     return entries;
308   }
309 
310   if (entry_type.IsNull() || type == PerformanceEntry::kResource) {
311     for (const auto& resource : resource_timing_buffer_) {
312       if (resource->name() == name)
313         entries.push_back(resource);
314     }
315   }
316 
317   if (entry_type.IsNull() || type == PerformanceEntry::kFirstInput) {
318     if (first_input_timing_ && first_input_timing_->name() == name)
319       entries.push_back(first_input_timing_);
320   }
321   if (type == PerformanceEntry::kFirstInput) {
322     UseCounter::Count(GetExecutionContext(),
323                       WebFeature::kEventTimingExplicitlyRequested);
324   }
325 
326   if (entry_type.IsNull() || type == PerformanceEntry::kNavigation) {
327     if (!navigation_timing_)
328       navigation_timing_ = CreateNavigationTimingInstance();
329     if (navigation_timing_ && navigation_timing_->name() == name)
330       entries.push_back(navigation_timing_);
331   }
332 
333   if (user_timing_) {
334     if (entry_type.IsNull() || type == PerformanceEntry::kMark)
335       entries.AppendVector(user_timing_->GetMarks(name));
336     if (entry_type.IsNull() || type == PerformanceEntry::kMeasure)
337       entries.AppendVector(user_timing_->GetMeasures(name));
338   }
339 
340   if (entry_type.IsNull() || type == PerformanceEntry::kPaint) {
341     if (first_paint_timing_ && first_paint_timing_->name() == name)
342       entries.push_back(first_paint_timing_);
343     if (first_contentful_paint_timing_ &&
344         first_contentful_paint_timing_->name() == name)
345       entries.push_back(first_contentful_paint_timing_);
346   }
347 
348   std::sort(entries.begin(), entries.end(),
349             PerformanceEntry::StartTimeCompareLessThan);
350   return entries;
351 }
352 
clearResourceTimings()353 void Performance::clearResourceTimings() {
354   resource_timing_buffer_.clear();
355 }
356 
setResourceTimingBufferSize(unsigned size)357 void Performance::setResourceTimingBufferSize(unsigned size) {
358   resource_timing_buffer_size_limit_ = size;
359 }
360 
PassesTimingAllowCheck(const ResourceResponse & response,const ResourceResponse & next_response,const SecurityOrigin & initiator_security_origin,ExecutionContext * context,bool * response_tainting_not_basic,bool * tainted_origin_flag)361 bool Performance::PassesTimingAllowCheck(
362     const ResourceResponse& response,
363     const ResourceResponse& next_response,
364     const SecurityOrigin& initiator_security_origin,
365     ExecutionContext* context,
366     bool* response_tainting_not_basic,
367     bool* tainted_origin_flag) {
368   DCHECK(response_tainting_not_basic);
369   DCHECK(tainted_origin_flag);
370   const KURL& response_url = response.ResponseUrl();
371   scoped_refptr<const SecurityOrigin> resource_origin =
372       SecurityOrigin::Create(response_url);
373   bool is_same_origin =
374       resource_origin->IsSameOriginWith(&initiator_security_origin);
375   if (!*response_tainting_not_basic && is_same_origin)
376     return true;
377   *response_tainting_not_basic = true;
378 
379   const AtomicString& timing_allow_origin_string =
380       response.HttpHeaderField(http_names::kTimingAllowOrigin);
381   if (timing_allow_origin_string.IsEmpty())
382     return false;
383 
384   const String& security_origin = initiator_security_origin.ToString();
385   CommaDelimitedHeaderSet tao_headers;
386   ParseCommaDelimitedHeader(timing_allow_origin_string, tao_headers);
387   if (tao_headers.size() == 1u) {
388     if (*tao_headers.begin() == "*") {
389       UseCounter::Count(context, WebFeature::kStarInTimingAllowOrigin);
390       return true;
391     } else {
392       UseCounter::Count(context, WebFeature::kSingleOriginInTimingAllowOrigin);
393     }
394   } else if (tao_headers.size() > 1u) {
395     UseCounter::Count(context, WebFeature::kMultipleOriginsInTimingAllowOrigin);
396   }
397   bool is_next_resource_same_origin = true;
398   // Only do the origin check if |next_response| is not equal to |response|.
399   if (&next_response != &response) {
400     is_next_resource_same_origin =
401         SecurityOrigin::Create(next_response.ResponseUrl())
402             ->IsSameOriginWith(resource_origin.get());
403   }
404   if (!is_same_origin && !is_next_resource_same_origin)
405     *tainted_origin_flag = true;
406   bool contains_security_origin = false;
407   for (const String& header : tao_headers) {
408     if (header == "*")
409       return true;
410 
411     if (header == security_origin)
412       contains_security_origin = true;
413   }
414 
415   // If the tainted origin flag is set and the header contains the origin, this
416   // means that this method currently passes the check but once we implement the
417   // tainted origin flag properly then it will fail the check. Record this in a
418   // UseCounter to track how many webpages contain resources where the new check
419   // would fail.
420   if (*tainted_origin_flag && contains_security_origin) {
421     UseCounter::Count(context,
422                       WebFeature::kResourceTimingTaintedOriginFlagFail);
423   }
424   return contains_security_origin;
425 }
426 
AllowsTimingRedirect(const Vector<ResourceResponse> & redirect_chain,const ResourceResponse & final_response,const SecurityOrigin & initiator_security_origin,ExecutionContext * context)427 bool Performance::AllowsTimingRedirect(
428     const Vector<ResourceResponse>& redirect_chain,
429     const ResourceResponse& final_response,
430     const SecurityOrigin& initiator_security_origin,
431     ExecutionContext* context) {
432   bool response_tainting_not_basic = false;
433   bool tainted_origin_flag = false;
434 
435   for (unsigned i = 0; i < redirect_chain.size(); ++i) {
436     const ResourceResponse& response = redirect_chain[i];
437     const ResourceResponse& next_response =
438         i + 1 < redirect_chain.size() ? redirect_chain[i + 1] : final_response;
439     if (!PassesTimingAllowCheck(
440             response, next_response, initiator_security_origin, context,
441             &response_tainting_not_basic, &tainted_origin_flag))
442       return false;
443   }
444   if (!PassesTimingAllowCheck(
445           final_response, final_response, initiator_security_origin, context,
446           &response_tainting_not_basic, &tainted_origin_flag)) {
447     return false;
448   }
449 
450   return true;
451 }
452 
GenerateAndAddResourceTiming(const ResourceTimingInfo & info,const AtomicString & initiator_type)453 void Performance::GenerateAndAddResourceTiming(
454     const ResourceTimingInfo& info,
455     const AtomicString& initiator_type) {
456   ExecutionContext* context = GetExecutionContext();
457   const SecurityOrigin* security_origin = GetSecurityOrigin(context);
458   if (!security_origin)
459     return;
460   // |info| is taken const-ref but this can make destructive changes to
461   // WorkerTimingContainer on |info| when a page is controlled by a service
462   // worker.
463   AddResourceTiming(
464       GenerateResourceTiming(*security_origin, info, *context),
465       !initiator_type.IsNull() ? initiator_type : info.InitiatorType(),
466       info.TakeWorkerTimingReceiver(), context);
467 }
468 
GenerateResourceTiming(const SecurityOrigin & destination_origin,const ResourceTimingInfo & info,ExecutionContext & context_for_use_counter)469 mojom::blink::ResourceTimingInfoPtr Performance::GenerateResourceTiming(
470     const SecurityOrigin& destination_origin,
471     const ResourceTimingInfo& info,
472     ExecutionContext& context_for_use_counter) {
473   // TODO(dcheng): It would be nicer if the performance entries simply held this
474   // data internally, rather than requiring it be marshalled back and forth.
475   const ResourceResponse& final_response = info.FinalResponse();
476   mojom::blink::ResourceTimingInfoPtr result =
477       mojom::blink::ResourceTimingInfo::New();
478   result->name = info.InitialURL().GetString();
479   result->start_time = info.InitialTime();
480   result->alpn_negotiated_protocol =
481       final_response.AlpnNegotiatedProtocol().IsNull()
482           ? g_empty_string
483           : final_response.AlpnNegotiatedProtocol();
484   result->connection_info = final_response.ConnectionInfoString().IsNull()
485                                 ? g_empty_string
486                                 : final_response.ConnectionInfoString();
487   result->timing = final_response.GetResourceLoadTiming()
488                        ? final_response.GetResourceLoadTiming()->ToMojo()
489                        : nullptr;
490   result->response_end = info.LoadResponseEnd();
491   result->context_type = info.ContextType();
492   result->request_destination = info.RequestDestination();
493 
494   bool response_tainting_not_basic = false;
495   bool tainted_origin_flag = false;
496   result->allow_timing_details = PassesTimingAllowCheck(
497       final_response, final_response, destination_origin,
498       &context_for_use_counter, &response_tainting_not_basic,
499       &tainted_origin_flag);
500 
501   const Vector<ResourceResponse>& redirect_chain = info.RedirectChain();
502   if (!redirect_chain.IsEmpty()) {
503     result->allow_redirect_details =
504         AllowsTimingRedirect(redirect_chain, final_response, destination_origin,
505                              &context_for_use_counter);
506 
507     // TODO(https://crbug.com/817691): is |last_chained_timing| being null a bug
508     // or is this if statement reasonable?
509     if (ResourceLoadTiming* last_chained_timing =
510             redirect_chain.back().GetResourceLoadTiming()) {
511       result->last_redirect_end_time = last_chained_timing->ReceiveHeadersEnd();
512     } else {
513       result->allow_redirect_details = false;
514       result->last_redirect_end_time = base::TimeTicks();
515     }
516     if (!result->allow_redirect_details) {
517       // TODO(https://crbug.com/817691): There was previously a DCHECK that
518       // |final_timing| is non-null. However, it clearly can be null: removing
519       // this check caused https://crbug.com/803811. Figure out how this can
520       // happen so test coverage can be added.
521       if (ResourceLoadTiming* final_timing =
522               final_response.GetResourceLoadTiming()) {
523         result->start_time = final_timing->RequestTime();
524       }
525     }
526   } else {
527     result->allow_redirect_details = false;
528     result->last_redirect_end_time = base::TimeTicks();
529   }
530 
531   result->transfer_size = info.TransferSize();
532   result->encoded_body_size = final_response.EncodedBodyLength();
533   result->decoded_body_size = final_response.DecodedBodyLength();
534   result->did_reuse_connection = final_response.ConnectionReused();
535   result->is_secure_context =
536       SecurityOrigin::IsSecure(final_response.ResponseUrl());
537   result->allow_negative_values = info.NegativeAllowed();
538 
539   if (result->allow_timing_details) {
540     result->server_timing =
541         PerformanceServerTiming::ParseServerTimingToMojo(info);
542   }
543   if (!result->server_timing.IsEmpty()) {
544     UseCounter::Count(&context_for_use_counter,
545                       WebFeature::kPerformanceServerTiming);
546   }
547 
548   return result;
549 }
550 
AddResourceTiming(mojom::blink::ResourceTimingInfoPtr info,const AtomicString & initiator_type,mojo::PendingReceiver<mojom::blink::WorkerTimingContainer> worker_timing_receiver,ExecutionContext * context)551 void Performance::AddResourceTiming(
552     mojom::blink::ResourceTimingInfoPtr info,
553     const AtomicString& initiator_type,
554     mojo::PendingReceiver<mojom::blink::WorkerTimingContainer>
555         worker_timing_receiver,
556     ExecutionContext* context) {
557   auto* entry = MakeGarbageCollected<PerformanceResourceTiming>(
558       *info, time_origin_, initiator_type, std::move(worker_timing_receiver),
559       context);
560   NotifyObserversOfEntry(*entry);
561   // https://w3c.github.io/resource-timing/#dfn-add-a-performanceresourcetiming-entry
562   if (CanAddResourceTimingEntry() &&
563       !resource_timing_buffer_full_event_pending_) {
564     resource_timing_buffer_.push_back(entry);
565     return;
566   }
567   if (!resource_timing_buffer_full_event_pending_) {
568     resource_timing_buffer_full_event_pending_ = true;
569     resource_timing_buffer_full_timer_.StartOneShot(base::TimeDelta(),
570                                                     FROM_HERE);
571   }
572   resource_timing_secondary_buffer_.push_back(entry);
573 }
574 
575 // Called after loadEventEnd happens.
NotifyNavigationTimingToObservers()576 void Performance::NotifyNavigationTimingToObservers() {
577   if (!navigation_timing_)
578     navigation_timing_ = CreateNavigationTimingInstance();
579   if (navigation_timing_)
580     NotifyObserversOfEntry(*navigation_timing_);
581 }
582 
IsElementTimingBufferFull() const583 bool Performance::IsElementTimingBufferFull() const {
584   return element_timing_buffer_.size() >= element_timing_buffer_max_size_;
585 }
586 
IsEventTimingBufferFull() const587 bool Performance::IsEventTimingBufferFull() const {
588   return event_timing_buffer_.size() >= event_timing_buffer_max_size_;
589 }
590 
CopySecondaryBuffer()591 void Performance::CopySecondaryBuffer() {
592   // https://w3c.github.io/resource-timing/#dfn-copy-secondary-buffer
593   while (!resource_timing_secondary_buffer_.empty() &&
594          CanAddResourceTimingEntry()) {
595     PerformanceEntry* entry = resource_timing_secondary_buffer_.front();
596     DCHECK(entry);
597     resource_timing_secondary_buffer_.pop_front();
598     resource_timing_buffer_.push_back(entry);
599   }
600 }
601 
FireResourceTimingBufferFull(TimerBase *)602 void Performance::FireResourceTimingBufferFull(TimerBase*) {
603   // https://w3c.github.io/resource-timing/#dfn-fire-a-buffer-full-event
604   while (!resource_timing_secondary_buffer_.empty()) {
605     int excess_entries_before = resource_timing_secondary_buffer_.size();
606     if (!CanAddResourceTimingEntry()) {
607       DispatchEvent(
608           *Event::Create(event_type_names::kResourcetimingbufferfull));
609     }
610     CopySecondaryBuffer();
611     int excess_entries_after = resource_timing_secondary_buffer_.size();
612     if (excess_entries_after >= excess_entries_before) {
613       resource_timing_secondary_buffer_.clear();
614       break;
615     }
616   }
617   resource_timing_buffer_full_event_pending_ = false;
618 }
619 
AddElementTimingBuffer(PerformanceElementTiming & entry)620 void Performance::AddElementTimingBuffer(PerformanceElementTiming& entry) {
621   if (!IsElementTimingBufferFull()) {
622     element_timing_buffer_.push_back(&entry);
623   }
624 }
625 
AddEventTimingBuffer(PerformanceEventTiming & entry)626 void Performance::AddEventTimingBuffer(PerformanceEventTiming& entry) {
627   DCHECK(RuntimeEnabledFeatures::EventTimingEnabled(GetExecutionContext()));
628   if (!IsEventTimingBufferFull()) {
629     event_timing_buffer_.push_back(&entry);
630   }
631 }
632 
AddLayoutShiftBuffer(LayoutShift & entry)633 void Performance::AddLayoutShiftBuffer(LayoutShift& entry) {
634   if (layout_shift_buffer_.size() < kDefaultLayoutShiftBufferSize)
635     layout_shift_buffer_.push_back(&entry);
636 }
637 
AddLargestContentfulPaint(LargestContentfulPaint * entry)638 void Performance::AddLargestContentfulPaint(LargestContentfulPaint* entry) {
639   if (largest_contentful_paint_buffer_.size() <
640       kDefaultLargestContenfulPaintSize) {
641     largest_contentful_paint_buffer_.push_back(entry);
642   }
643 }
644 
AddFirstPaintTiming(base::TimeTicks start_time)645 void Performance::AddFirstPaintTiming(base::TimeTicks start_time) {
646   AddPaintTiming(PerformancePaintTiming::PaintType::kFirstPaint, start_time);
647 }
648 
AddFirstContentfulPaintTiming(base::TimeTicks start_time)649 void Performance::AddFirstContentfulPaintTiming(base::TimeTicks start_time) {
650   AddPaintTiming(PerformancePaintTiming::PaintType::kFirstContentfulPaint,
651                  start_time);
652 }
653 
AddPaintTiming(PerformancePaintTiming::PaintType type,base::TimeTicks start_time)654 void Performance::AddPaintTiming(PerformancePaintTiming::PaintType type,
655                                  base::TimeTicks start_time) {
656   PerformanceEntry* entry = MakeGarbageCollected<PerformancePaintTiming>(
657       type, MonotonicTimeToDOMHighResTimeStamp(start_time));
658   // Always buffer First Paint & First Contentful Paint.
659   if (type == PerformancePaintTiming::PaintType::kFirstPaint)
660     first_paint_timing_ = entry;
661   else if (type == PerformancePaintTiming::PaintType::kFirstContentfulPaint)
662     first_contentful_paint_timing_ = entry;
663   NotifyObserversOfEntry(*entry);
664 }
665 
CanAddResourceTimingEntry()666 bool Performance::CanAddResourceTimingEntry() {
667   // https://w3c.github.io/resource-timing/#dfn-can-add-resource-timing-entry
668   return resource_timing_buffer_.size() < resource_timing_buffer_size_limit_;
669 }
670 
AddLongTaskTiming(base::TimeTicks start_time,base::TimeTicks end_time,const AtomicString & name,const AtomicString & container_type,const String & container_src,const String & container_id,const String & container_name)671 void Performance::AddLongTaskTiming(base::TimeTicks start_time,
672                                     base::TimeTicks end_time,
673                                     const AtomicString& name,
674                                     const AtomicString& container_type,
675                                     const String& container_src,
676                                     const String& container_id,
677                                     const String& container_name) {
678   auto* entry = MakeGarbageCollected<PerformanceLongTaskTiming>(
679       MonotonicTimeToDOMHighResTimeStamp(start_time),
680       MonotonicTimeToDOMHighResTimeStamp(end_time), name, container_type,
681       container_src, container_id, container_name);
682   if (longtask_buffer_.size() < kDefaultLongTaskBufferSize) {
683     longtask_buffer_.push_back(entry);
684   } else {
685     UseCounter::Count(GetExecutionContext(), WebFeature::kLongTaskBufferFull);
686   }
687   NotifyObserversOfEntry(*entry);
688 }
689 
GetUserTiming()690 UserTiming& Performance::GetUserTiming() {
691   if (!user_timing_)
692     user_timing_ = MakeGarbageCollected<UserTiming>(*this);
693   return *user_timing_;
694 }
695 
mark(ScriptState * script_state,const AtomicString & mark_name,PerformanceMarkOptions * mark_options,ExceptionState & exception_state)696 PerformanceMark* Performance::mark(ScriptState* script_state,
697                                    const AtomicString& mark_name,
698                                    PerformanceMarkOptions* mark_options,
699                                    ExceptionState& exception_state) {
700   if (mark_options &&
701       (mark_options->hasStartTime() || mark_options->hasDetail())) {
702     UseCounter::Count(GetExecutionContext(), WebFeature::kUserTimingL3);
703   }
704   PerformanceMark* performance_mark = PerformanceMark::Create(
705       script_state, mark_name, mark_options, exception_state);
706   if (performance_mark) {
707     GetUserTiming().AddMarkToPerformanceTimeline(*performance_mark);
708     NotifyObserversOfEntry(*performance_mark);
709   }
710   return performance_mark;
711 }
712 
clearMarks(const AtomicString & mark_name)713 void Performance::clearMarks(const AtomicString& mark_name) {
714   GetUserTiming().ClearMarks(mark_name);
715 }
716 
measure(ScriptState * script_state,const AtomicString & measure_name,ExceptionState & exception_state)717 PerformanceMeasure* Performance::measure(ScriptState* script_state,
718                                          const AtomicString& measure_name,
719                                          ExceptionState& exception_state) {
720   // When |startOrOptions| is not provided, it's assumed to be an empty
721   // dictionary.
722   return MeasureInternal(
723       script_state, measure_name,
724       StringOrPerformanceMeasureOptions::FromPerformanceMeasureOptions(
725           PerformanceMeasureOptions::Create()),
726       base::nullopt, exception_state);
727 }
728 
measure(ScriptState * script_state,const AtomicString & measure_name,const StringOrPerformanceMeasureOptions & start_or_options,ExceptionState & exception_state)729 PerformanceMeasure* Performance::measure(
730     ScriptState* script_state,
731     const AtomicString& measure_name,
732     const StringOrPerformanceMeasureOptions& start_or_options,
733     ExceptionState& exception_state) {
734   return MeasureInternal(script_state, measure_name, start_or_options,
735                          base::nullopt, exception_state);
736 }
737 
measure(ScriptState * script_state,const AtomicString & measure_name,const StringOrPerformanceMeasureOptions & start_or_options,const String & end,ExceptionState & exception_state)738 PerformanceMeasure* Performance::measure(
739     ScriptState* script_state,
740     const AtomicString& measure_name,
741     const StringOrPerformanceMeasureOptions& start_or_options,
742     const String& end,
743     ExceptionState& exception_state) {
744   return MeasureInternal(script_state, measure_name, start_or_options,
745                          base::Optional<String>(end), exception_state);
746 }
747 
748 // |MeasureInternal| exists to unify the arguments from different
749 // `performance.measure()` overloads into a consistent form, then delegate to
750 // |MeasureWithDetail|.
751 //
752 // |start_or_options| is either a String or a dictionary of options. When it's
753 // a String, it represents a starting performance mark. When it's a dictionary,
754 // the allowed fields are 'start', 'duration', 'end' and 'detail'. However,
755 // there are some combinations of fields and parameters which must raise
756 // errors. Specifically, the spec (https://https://w3c.github.io/user-timing/)
757 // requires errors to thrown in the following cases:
758 //  - If |start_or_options| is a dictionary and 'end_mark' is passed.
759 //  - If an options dictionary contains neither a 'start' nor an 'end' field.
760 //  - If an options dictionary contains all of 'start', 'duration' and 'end'.
761 //
762 // |end_mark| will be base::nullopt unless the `performance.measure()` overload
763 // specified an end mark.
MeasureInternal(ScriptState * script_state,const AtomicString & measure_name,const StringOrPerformanceMeasureOptions & start_or_options,base::Optional<String> end_mark,ExceptionState & exception_state)764 PerformanceMeasure* Performance::MeasureInternal(
765     ScriptState* script_state,
766     const AtomicString& measure_name,
767     const StringOrPerformanceMeasureOptions& start_or_options,
768     base::Optional<String> end_mark,
769     ExceptionState& exception_state) {
770   DCHECK(!start_or_options.IsNull());
771   // An empty option is treated with no difference as null, undefined.
772   if (start_or_options.IsPerformanceMeasureOptions() &&
773       !IsMeasureOptionsEmpty(
774           *start_or_options.GetAsPerformanceMeasureOptions())) {
775     UseCounter::Count(GetExecutionContext(), WebFeature::kUserTimingL3);
776     // measure("name", { start, end }, *)
777     if (end_mark) {
778       exception_state.ThrowTypeError(
779           "If a non-empty PerformanceMeasureOptions object was passed, "
780           "|end_mark| must not be passed.");
781       return nullptr;
782     }
783     const PerformanceMeasureOptions* options =
784         start_or_options.GetAsPerformanceMeasureOptions();
785     if (!options->hasStart() && !options->hasEnd()) {
786       exception_state.ThrowTypeError(
787           "If a non-empty PerformanceMeasureOptions object was passed, at "
788           "least one of its 'start' or 'end' properties must be present.");
789       return nullptr;
790     }
791 
792     if (options->hasStart() && options->hasDuration() && options->hasEnd()) {
793       exception_state.ThrowTypeError(
794           "If a non-empty PerformanceMeasureOptions object was passed, it "
795           "must not have all of its 'start', 'duration', and 'end' "
796           "properties defined");
797       return nullptr;
798     }
799 
800     base::Optional<StringOrDouble> start;
801     if (options->hasStart()) {
802       start = options->start();
803     }
804     base::Optional<double> duration;
805     if (options->hasDuration()) {
806       duration = options->duration();
807     }
808     base::Optional<StringOrDouble> end;
809     if (options->hasEnd()) {
810       end = options->end();
811     }
812 
813     return MeasureWithDetail(
814         script_state, measure_name, start, duration, end,
815         options->hasDetail() ? options->detail() : ScriptValue(),
816         exception_state);
817   }
818 
819   // measure("name", "mark1", *)
820   base::Optional<StringOrDouble> start;
821   if (start_or_options.IsString()) {
822     start = StringOrDouble::FromString(start_or_options.GetAsString());
823   }
824   // We let |end_mark| behave the same whether it's empty, undefined or null
825   // in JS, as long as |end_mark| is null in C++.
826   base::Optional<StringOrDouble> end;
827   if (end_mark) {
828     end = StringOrDouble::FromString(*end_mark);
829   }
830   return MeasureWithDetail(script_state, measure_name, start,
831                            /* duration = */ base::nullopt, end,
832                            ScriptValue::CreateNull(script_state->GetIsolate()),
833                            exception_state);
834 }
835 
MeasureWithDetail(ScriptState * script_state,const AtomicString & measure_name,const base::Optional<StringOrDouble> & start,const base::Optional<double> & duration,const base::Optional<StringOrDouble> & end,const ScriptValue & detail,ExceptionState & exception_state)836 PerformanceMeasure* Performance::MeasureWithDetail(
837     ScriptState* script_state,
838     const AtomicString& measure_name,
839     const base::Optional<StringOrDouble>& start,
840     const base::Optional<double>& duration,
841     const base::Optional<StringOrDouble>& end,
842     const ScriptValue& detail,
843     ExceptionState& exception_state) {
844   PerformanceMeasure* performance_measure =
845       GetUserTiming().Measure(script_state, measure_name, start, duration, end,
846                               detail, exception_state);
847   if (performance_measure)
848     NotifyObserversOfEntry(*performance_measure);
849   return performance_measure;
850 }
851 
clearMeasures(const AtomicString & measure_name)852 void Performance::clearMeasures(const AtomicString& measure_name) {
853   GetUserTiming().ClearMeasures(measure_name);
854 }
855 
profile(ScriptState * script_state,const ProfilerInitOptions * options,ExceptionState & exception_state)856 ScriptPromise Performance::profile(ScriptState* script_state,
857                                    const ProfilerInitOptions* options,
858                                    ExceptionState& exception_state) {
859   auto* execution_context = ExecutionContext::From(script_state);
860   DCHECK(execution_context);
861   DCHECK(
862       RuntimeEnabledFeatures::ExperimentalJSProfilerEnabled(execution_context));
863 
864   if (!execution_context->CrossOriginIsolatedCapability()) {
865     exception_state.ThrowSecurityError(
866         "performance.profile() requires COOP+COEP (web.dev/coop-coep)");
867     return ScriptPromise();
868   }
869 
870   auto* profiler_group = ProfilerGroup::From(script_state->GetIsolate());
871   DCHECK(profiler_group);
872 
873   auto* profiler = profiler_group->CreateProfiler(
874       script_state, *options, time_origin_, exception_state);
875   if (exception_state.HadException())
876     return ScriptPromise();
877 
878   return ScriptPromise::Cast(script_state, ToV8(profiler, script_state));
879 }
880 
RegisterPerformanceObserver(PerformanceObserver & observer)881 void Performance::RegisterPerformanceObserver(PerformanceObserver& observer) {
882   observer_filter_options_ |= observer.FilterOptions();
883   observers_.insert(&observer);
884 }
885 
UnregisterPerformanceObserver(PerformanceObserver & old_observer)886 void Performance::UnregisterPerformanceObserver(
887     PerformanceObserver& old_observer) {
888   observers_.erase(&old_observer);
889   UpdatePerformanceObserverFilterOptions();
890 }
891 
UpdatePerformanceObserverFilterOptions()892 void Performance::UpdatePerformanceObserverFilterOptions() {
893   observer_filter_options_ = PerformanceEntry::kInvalid;
894   for (const auto& observer : observers_) {
895     observer_filter_options_ |= observer->FilterOptions();
896   }
897 }
898 
NotifyObserversOfEntry(PerformanceEntry & entry) const899 void Performance::NotifyObserversOfEntry(PerformanceEntry& entry) const {
900   DCHECK(entry.EntryTypeEnum() != PerformanceEntry::kEvent ||
901          RuntimeEnabledFeatures::EventTimingEnabled(GetExecutionContext()));
902   bool observer_found = false;
903   for (auto& observer : observers_) {
904     if (observer->FilterOptions() & entry.EntryTypeEnum() &&
905         observer->CanObserve(entry)) {
906       observer->EnqueuePerformanceEntry(entry);
907       observer_found = true;
908     }
909   }
910   if (observer_found && entry.EntryTypeEnum() == PerformanceEntry::kPaint)
911     UseCounter::Count(GetExecutionContext(), WebFeature::kPaintTimingObserved);
912 }
913 
HasObserverFor(PerformanceEntry::EntryType filter_type) const914 bool Performance::HasObserverFor(
915     PerformanceEntry::EntryType filter_type) const {
916   return observer_filter_options_ & filter_type;
917 }
918 
ActivateObserver(PerformanceObserver & observer)919 void Performance::ActivateObserver(PerformanceObserver& observer) {
920   if (active_observers_.IsEmpty())
921     deliver_observations_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
922 
923   if (suspended_observers_.Contains(&observer))
924     suspended_observers_.erase(&observer);
925   active_observers_.insert(&observer);
926 }
927 
SuspendObserver(PerformanceObserver & observer)928 void Performance::SuspendObserver(PerformanceObserver& observer) {
929   DCHECK(!suspended_observers_.Contains(&observer));
930   if (!active_observers_.Contains(&observer))
931     return;
932   active_observers_.erase(&observer);
933   suspended_observers_.insert(&observer);
934 }
935 
DeliverObservationsTimerFired(TimerBase *)936 void Performance::DeliverObservationsTimerFired(TimerBase*) {
937   decltype(active_observers_) observers;
938   active_observers_.Swap(observers);
939   for (const auto& observer : observers)
940     observer->Deliver();
941 }
942 
943 // static
ClampTimeResolution(double time_seconds)944 double Performance::ClampTimeResolution(double time_seconds) {
945   DEFINE_THREAD_SAFE_STATIC_LOCAL(TimeClamper, clamper, ());
946   return clamper.ClampTimeResolution(time_seconds);
947 }
948 
949 // static
MonotonicTimeToDOMHighResTimeStamp(base::TimeTicks time_origin,base::TimeTicks monotonic_time,bool allow_negative_value)950 DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp(
951     base::TimeTicks time_origin,
952     base::TimeTicks monotonic_time,
953     bool allow_negative_value) {
954   // Avoid exposing raw platform timestamps.
955   if (monotonic_time.is_null() || time_origin.is_null())
956     return 0.0;
957 
958   double clamped_time_in_seconds =
959       ClampTimeResolution(monotonic_time.since_origin().InSecondsF()) -
960       ClampTimeResolution(time_origin.since_origin().InSecondsF());
961   if (clamped_time_in_seconds < 0 && !allow_negative_value)
962     return 0.0;
963   return ConvertSecondsToDOMHighResTimeStamp(clamped_time_in_seconds);
964 }
965 
966 // static
MonotonicTimeToTimeDelta(base::TimeTicks time_origin,base::TimeTicks monotonic_time,bool allow_negative_value)967 base::TimeDelta Performance::MonotonicTimeToTimeDelta(
968     base::TimeTicks time_origin,
969     base::TimeTicks monotonic_time,
970     bool allow_negative_value) {
971   return base::TimeDelta::FromMillisecondsD(MonotonicTimeToDOMHighResTimeStamp(
972       time_origin, monotonic_time, allow_negative_value));
973 }
974 
MonotonicTimeToDOMHighResTimeStamp(base::TimeTicks monotonic_time) const975 DOMHighResTimeStamp Performance::MonotonicTimeToDOMHighResTimeStamp(
976     base::TimeTicks monotonic_time) const {
977   return MonotonicTimeToDOMHighResTimeStamp(time_origin_, monotonic_time,
978                                             false /* allow_negative_value */);
979 }
980 
MonotonicTimeToTimeDelta(base::TimeTicks monotonic_time) const981 base::TimeDelta Performance::MonotonicTimeToTimeDelta(
982     base::TimeTicks monotonic_time) const {
983   return MonotonicTimeToTimeDelta(time_origin_, monotonic_time,
984                                   false /* allow_negative_value */);
985 }
986 
now() const987 DOMHighResTimeStamp Performance::now() const {
988   return MonotonicTimeToDOMHighResTimeStamp(tick_clock_->NowTicks());
989 }
990 
991 // static
CanExposeNode(Node * node)992 bool Performance::CanExposeNode(Node* node) {
993   if (!node || !node->isConnected() || node->IsInShadowTree())
994     return false;
995 
996   // Do not expose |node| when the document is not 'fully active'.
997   const Document& document = node->GetDocument();
998   if (!document.IsActive() || !document.GetFrame())
999     return false;
1000 
1001   return true;
1002 }
1003 
toJSONForBinding(ScriptState * script_state) const1004 ScriptValue Performance::toJSONForBinding(ScriptState* script_state) const {
1005   V8ObjectBuilder result(script_state);
1006   BuildJSONValue(result);
1007   return result.GetScriptValue();
1008 }
1009 
BuildJSONValue(V8ObjectBuilder & builder) const1010 void Performance::BuildJSONValue(V8ObjectBuilder& builder) const {
1011   builder.AddNumber("timeOrigin", timeOrigin());
1012   // |memory| is not part of the spec, omitted.
1013 }
1014 
Trace(Visitor * visitor) const1015 void Performance::Trace(Visitor* visitor) const {
1016   visitor->Trace(resource_timing_buffer_);
1017   visitor->Trace(resource_timing_secondary_buffer_);
1018   visitor->Trace(element_timing_buffer_);
1019   visitor->Trace(event_timing_buffer_);
1020   visitor->Trace(layout_shift_buffer_);
1021   visitor->Trace(largest_contentful_paint_buffer_);
1022   visitor->Trace(longtask_buffer_);
1023   visitor->Trace(visibility_state_buffer_);
1024   visitor->Trace(navigation_timing_);
1025   visitor->Trace(user_timing_);
1026   visitor->Trace(first_paint_timing_);
1027   visitor->Trace(first_contentful_paint_timing_);
1028   visitor->Trace(first_input_timing_);
1029   visitor->Trace(observers_);
1030   visitor->Trace(active_observers_);
1031   visitor->Trace(suspended_observers_);
1032   EventTargetWithInlineData::Trace(visitor);
1033 }
1034 
SetClocksForTesting(const base::Clock * clock,const base::TickClock * tick_clock)1035 void Performance::SetClocksForTesting(const base::Clock* clock,
1036                                       const base::TickClock* tick_clock) {
1037   tick_clock_ = tick_clock;
1038   // Recompute |unix_at_zero_monotonic_|.
1039   unix_at_zero_monotonic_ = ConvertSecondsToDOMHighResTimeStamp(
1040       clock->Now().ToDoubleT() -
1041       tick_clock_->NowTicks().since_origin().InSecondsF());
1042 }
1043 
ResetTimeOriginForTesting(base::TimeTicks time_origin)1044 void Performance::ResetTimeOriginForTesting(base::TimeTicks time_origin) {
1045   time_origin_ = time_origin;
1046 }
1047 
1048 }  // namespace blink
1049