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