OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/page_load_metrics/page_load_tracker.h" | 5 #include "chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h " |
6 | 6 |
7 #include <algorithm> | |
8 #include <ostream> | |
Charlie Harrison
2017/05/24 21:02:51
shouldn't be removed
Bryan McQuade
2017/05/25 02:30:21
done
| |
9 #include <string> | |
10 #include <utility> | 7 #include <utility> |
11 | 8 |
12 #include "base/logging.h" | |
Charlie Harrison
2017/05/24 21:02:51
shouldn't be removed
Bryan McQuade
2017/05/25 02:30:21
done
| |
13 #include "base/memory/ptr_util.h" | |
14 #include "base/metrics/histogram_macros.h" | |
Charlie Harrison
2017/05/24 21:02:51
We're still using UMA_HISTOGRAM_ENUMERATION
Bryan McQuade
2017/05/25 02:30:21
done
| |
15 #include "chrome/browser/page_load_metrics/browser_page_track_decider.h" | 9 #include "chrome/browser/page_load_metrics/browser_page_track_decider.h" |
16 #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface. h" | 10 #include "chrome/browser/page_load_metrics/page_load_metrics_embedder_interface. h" |
17 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" | 11 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" |
18 #include "chrome/browser/prerender/prerender_contents.h" | 12 #include "chrome/browser/page_load_metrics/page_load_tracker.h" |
19 #include "chrome/common/page_load_metrics/page_load_timing.h" | 13 #include "chrome/common/page_load_metrics/page_load_metrics_util.h" |
20 #include "content/public/browser/navigation_details.h" | |
21 #include "content/public/browser/navigation_handle.h" | 14 #include "content/public/browser/navigation_handle.h" |
22 #include "content/public/browser/render_frame_host.h" | 15 #include "content/public/browser/render_frame_host.h" |
23 #include "content/public/browser/web_contents.h" | |
24 #include "content/public/browser/web_contents_observer.h" | |
25 #include "content/public/common/browser_side_navigation_policy.h" | |
26 #include "ui/base/page_transition_types.h" | |
27 | |
28 // This macro invokes the specified method on each observer, passing the | |
29 // variable length arguments as the method's arguments, and removes the observer | |
30 // from the list of observers if the given method returns STOP_OBSERVING. | |
31 #define INVOKE_AND_PRUNE_OBSERVERS(observers, Method, ...) \ | |
32 for (auto it = observers.begin(); it != observers.end();) { \ | |
33 if ((*it)->Method(__VA_ARGS__) == \ | |
34 PageLoadMetricsObserver::STOP_OBSERVING) { \ | |
35 it = observers.erase(it); \ | |
36 } else { \ | |
37 ++it; \ | |
38 } \ | |
39 } | |
40 | 16 |
41 namespace page_load_metrics { | 17 namespace page_load_metrics { |
42 | 18 |
43 namespace internal { | 19 namespace internal { |
44 | 20 |
45 const char kErrorEvents[] = "PageLoad.Internal.ErrorCode"; | |
46 const char kAbortChainSizeReload[] = | |
47 "PageLoad.Internal.ProvisionalAbortChainSize.Reload"; | |
48 const char kAbortChainSizeForwardBack[] = | |
49 "PageLoad.Internal.ProvisionalAbortChainSize.ForwardBack"; | |
50 const char kAbortChainSizeNewNavigation[] = | |
51 "PageLoad.Internal.ProvisionalAbortChainSize.NewNavigation"; | |
52 const char kAbortChainSizeSameURL[] = | |
53 "PageLoad.Internal.ProvisionalAbortChainSize.SameURL"; | |
54 const char kAbortChainSizeNoCommit[] = | |
55 "PageLoad.Internal.ProvisionalAbortChainSize.NoCommit"; | |
56 const char kClientRedirectFirstPaintToNavigation[] = | |
57 "PageLoad.Internal.ClientRedirect.FirstPaintToNavigation"; | |
58 const char kClientRedirectWithoutPaint[] = | |
59 "PageLoad.Internal.ClientRedirect.NavigationWithoutPaint"; | |
60 const char kPageLoadCompletedAfterAppBackground[] = | |
61 "PageLoad.Internal.PageLoadCompleted.AfterAppBackground"; | |
62 const char kPageLoadStartedInForeground[] = | |
63 "PageLoad.Internal.NavigationStartedInForeground"; | |
64 const char kPageLoadPrerender[] = "PageLoad.Internal.Prerender"; | |
65 const char kPageLoadTimingStatus[] = "PageLoad.Internal.PageLoadTimingStatus"; | 21 const char kPageLoadTimingStatus[] = "PageLoad.Internal.PageLoadTimingStatus"; |
66 const char kPageLoadTimingDispatchStatus[] = | 22 const char kPageLoadTimingDispatchStatus[] = |
67 "PageLoad.Internal.PageLoadTimingStatus.AtTimingCallbackDispatch"; | 23 "PageLoad.Internal.PageLoadTimingStatus.AtTimingCallbackDispatch"; |
68 | 24 |
69 } // namespace internal | 25 } // namespace internal |
70 | 26 |
71 void RecordInternalError(InternalErrorLoadEvent event) { | |
72 UMA_HISTOGRAM_ENUMERATION(internal::kErrorEvents, event, ERR_LAST_ENTRY); | |
73 } | |
74 | |
75 // TODO(csharrison): Add a case for client side redirects, which is what JS | |
76 // initiated window.location / window.history navigations get set to. | |
77 PageEndReason EndReasonForPageTransition(ui::PageTransition transition) { | |
78 if (transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) { | |
79 return END_CLIENT_REDIRECT; | |
80 } | |
81 if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) | |
82 return END_RELOAD; | |
83 if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) | |
84 return END_FORWARD_BACK; | |
85 if (ui::PageTransitionIsNewNavigation(transition)) | |
86 return END_NEW_NAVIGATION; | |
87 NOTREACHED() | |
88 << "EndReasonForPageTransition received unexpected ui::PageTransition: " | |
89 << transition; | |
90 return END_OTHER; | |
91 } | |
92 | |
93 void LogAbortChainSameURLHistogram(int aborted_chain_size_same_url) { | |
94 if (aborted_chain_size_same_url > 0) { | |
95 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeSameURL, | |
96 aborted_chain_size_same_url); | |
97 } | |
98 } | |
99 | |
100 bool IsNavigationUserInitiated(content::NavigationHandle* handle) { | |
101 // TODO(crbug.com/617904): Browser initiated navigations should have | |
102 // HasUserGesture() set to true. In the meantime, we consider all | |
103 // browser-initiated navigations to be user initiated. | |
104 // | |
105 // TODO(crbug.com/637345): Some browser-initiated navigations incorrectly | |
106 // report that they are renderer-initiated. We will currently report that | |
107 // these navigations are not user initiated, when in fact they are user | |
108 // initiated. | |
109 return handle->HasUserGesture() || !handle->IsRendererInitiated(); | |
110 } | |
111 | |
112 namespace { | 27 namespace { |
113 | 28 |
114 // Helper to allow use of Optional<> values in LOG() messages. | 29 // Helper to allow use of Optional<> values in LOG() messages. |
115 std::ostream& operator<<(std::ostream& os, | 30 std::ostream& operator<<(std::ostream& os, |
116 const base::Optional<base::TimeDelta>& opt) { | 31 const base::Optional<base::TimeDelta>& opt) { |
Charlie Harrison
2017/05/24 21:02:51
include optional
| |
117 if (opt) | 32 if (opt) |
118 os << opt.value(); | 33 os << opt.value(); |
119 else | 34 else |
120 os << "(unset)"; | 35 os << "(unset)"; |
121 return os; | 36 return os; |
122 } | 37 } |
123 | 38 |
124 // If second is non-zero, first must also be non-zero and less than or equal to | 39 // If second is non-zero, first must also be non-zero and less than or equal to |
125 // second. | 40 // second. |
126 bool EventsInOrder(const base::Optional<base::TimeDelta>& first, | 41 bool EventsInOrder(const base::Optional<base::TimeDelta>& first, |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
309 // occasionally, as inter-frame updates can arrive out of order. Record a | 224 // occasionally, as inter-frame updates can arrive out of order. Record a |
310 // histogram to track how frequently it happens, along with the magnitude | 225 // histogram to track how frequently it happens, along with the magnitude |
311 // of the delta. | 226 // of the delta. |
312 PAGE_LOAD_HISTOGRAM("PageLoad.Internal.OutOfOrderInterFrameTiming", | 227 PAGE_LOAD_HISTOGRAM("PageLoad.Internal.OutOfOrderInterFrameTiming", |
313 inout_existing_value->value() - candidate_new_value); | 228 inout_existing_value->value() - candidate_new_value); |
314 } | 229 } |
315 | 230 |
316 *inout_existing_value = candidate_new_value; | 231 *inout_existing_value = candidate_new_value; |
317 } | 232 } |
318 | 233 |
319 void RecordAppBackgroundPageLoadCompleted(bool completed_after_background) { | |
320 UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadCompletedAfterAppBackground, | |
321 completed_after_background); | |
322 } | |
323 | |
324 void DispatchObserverTimingCallbacks( | |
325 PageLoadMetricsObserver* observer, | |
326 const mojom::PageLoadTiming& last_timing, | |
327 const mojom::PageLoadTiming& new_timing, | |
328 const mojom::PageLoadMetadata& last_metadata, | |
329 const PageLoadExtraInfo& extra_info) { | |
330 if (extra_info.main_frame_metadata.behavior_flags != | |
331 last_metadata.behavior_flags) | |
332 observer->OnLoadingBehaviorObserved(extra_info); | |
333 if (!last_timing.Equals(new_timing)) | |
334 observer->OnTimingUpdate(new_timing, extra_info); | |
335 if (new_timing.document_timing->dom_content_loaded_event_start && | |
336 !last_timing.document_timing->dom_content_loaded_event_start) | |
337 observer->OnDomContentLoadedEventStart(new_timing, extra_info); | |
338 if (new_timing.document_timing->load_event_start && | |
339 !last_timing.document_timing->load_event_start) | |
340 observer->OnLoadEventStart(new_timing, extra_info); | |
341 if (new_timing.document_timing->first_layout && | |
342 !last_timing.document_timing->first_layout) | |
343 observer->OnFirstLayout(new_timing, extra_info); | |
344 if (new_timing.paint_timing->first_paint && | |
345 !last_timing.paint_timing->first_paint) | |
346 observer->OnFirstPaintInPage(new_timing, extra_info); | |
347 if (new_timing.paint_timing->first_text_paint && | |
348 !last_timing.paint_timing->first_text_paint) | |
349 observer->OnFirstTextPaintInPage(new_timing, extra_info); | |
350 if (new_timing.paint_timing->first_image_paint && | |
351 !last_timing.paint_timing->first_image_paint) | |
352 observer->OnFirstImagePaintInPage(new_timing, extra_info); | |
353 if (new_timing.paint_timing->first_contentful_paint && | |
354 !last_timing.paint_timing->first_contentful_paint) | |
355 observer->OnFirstContentfulPaintInPage(new_timing, extra_info); | |
356 if (new_timing.paint_timing->first_meaningful_paint && | |
357 !last_timing.paint_timing->first_meaningful_paint) | |
358 observer->OnFirstMeaningfulPaintInMainFrameDocument(new_timing, extra_info); | |
359 if (new_timing.parse_timing->parse_start && | |
360 !last_timing.parse_timing->parse_start) | |
361 observer->OnParseStart(new_timing, extra_info); | |
362 if (new_timing.parse_timing->parse_stop && | |
363 !last_timing.parse_timing->parse_stop) | |
364 observer->OnParseStop(new_timing, extra_info); | |
365 } | |
366 | |
367 } // namespace | 234 } // namespace |
368 | 235 |
369 PageLoadTracker::PageLoadTracker( | 236 PageLoadMetricsUpdateDispatcher::PageLoadMetricsUpdateDispatcher( |
370 bool in_foreground, | 237 PageLoadMetricsUpdateDispatcher::Client* client, |
371 PageLoadMetricsEmbedderInterface* embedder_interface, | |
372 const GURL& currently_committed_url, | |
373 content::NavigationHandle* navigation_handle, | 238 content::NavigationHandle* navigation_handle, |
374 UserInitiatedInfo user_initiated_info, | 239 PageLoadMetricsEmbedderInterface* embedder_interface) |
375 int aborted_chain_size, | 240 : client_(client), |
376 int aborted_chain_size_same_url) | 241 embedder_interface_(embedder_interface), |
377 : did_stop_tracking_(false), | |
378 app_entered_background_(false), | |
379 navigation_start_(navigation_handle->NavigationStart()), | 242 navigation_start_(navigation_handle->NavigationStart()), |
380 url_(navigation_handle->GetURL()), | 243 current_merged_page_timing_(CreatePageLoadTiming()), |
381 start_url_(navigation_handle->GetURL()), | 244 pending_merged_page_timing_(CreatePageLoadTiming()), |
382 did_commit_(false), | 245 main_frame_metadata_(mojom::PageLoadMetadata::New()), |
383 page_end_reason_(END_NONE), | 246 subframe_metadata_(mojom::PageLoadMetadata::New()) {} |
384 page_end_user_initiated_info_(UserInitiatedInfo::NotUserInitiated()), | |
385 started_in_foreground_(in_foreground), | |
386 merged_page_timing_(CreatePageLoadTiming()), | |
387 last_dispatched_merged_page_timing_(CreatePageLoadTiming()), | |
388 last_dispatched_main_frame_metadata_(mojom::PageLoadMetadata::New()), | |
389 page_transition_(navigation_handle->GetPageTransition()), | |
390 user_initiated_info_(user_initiated_info), | |
391 aborted_chain_size_(aborted_chain_size), | |
392 aborted_chain_size_same_url_(aborted_chain_size_same_url), | |
393 embedder_interface_(embedder_interface) { | |
394 DCHECK(!navigation_handle->HasCommitted()); | |
395 embedder_interface_->RegisterObservers(this); | |
396 INVOKE_AND_PRUNE_OBSERVERS(observers_, OnStart, navigation_handle, | |
397 currently_committed_url, started_in_foreground_); | |
398 | 247 |
399 UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadStartedInForeground, | 248 PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {} |
400 started_in_foreground_); | 249 |
401 const bool is_prerender = prerender::PrerenderContents::FromWebContents( | 250 void PageLoadMetricsUpdateDispatcher::UpdateMetrics( |
402 navigation_handle->GetWebContents()) != nullptr; | 251 content::RenderFrameHost* render_frame_host, |
403 if (is_prerender) { | 252 const mojom::PageLoadTiming& new_timing, |
404 UMA_HISTOGRAM_BOOLEAN(internal::kPageLoadPrerender, true); | 253 const mojom::PageLoadMetadata& new_metadata) { |
254 if (render_frame_host->GetParent() == nullptr) { | |
255 UpdateMainFrameMetadata(new_metadata); | |
256 UpdateMainFrameTiming(new_timing); | |
257 } else { | |
258 UpdateSubFrameMetadata(new_metadata); | |
259 UpdateSubFrameTiming(render_frame_host, new_timing); | |
405 } | 260 } |
406 } | 261 } |
407 | 262 |
408 PageLoadTracker::~PageLoadTracker() { | 263 void PageLoadMetricsUpdateDispatcher::DidFinishSubFrameNavigation( |
409 if (app_entered_background_) { | |
410 RecordAppBackgroundPageLoadCompleted(true); | |
411 } | |
412 | |
413 if (did_stop_tracking_) | |
414 return; | |
415 | |
416 if (page_end_time_.is_null()) { | |
417 // page_end_time_ can be unset in some cases, such as when a navigation is | |
418 // aborted by a navigation that started before it. In these cases, set the | |
419 // end time to the current time. | |
420 RecordInternalError(ERR_NO_PAGE_LOAD_END_TIME); | |
421 NotifyPageEnd(END_OTHER, UserInitiatedInfo::NotUserInitiated(), | |
422 base::TimeTicks::Now(), true); | |
423 } | |
424 | |
425 if (!did_commit_) { | |
426 if (!failed_provisional_load_info_) | |
427 RecordInternalError(ERR_NO_COMMIT_OR_FAILED_PROVISIONAL_LOAD); | |
428 | |
429 // Don't include any aborts that resulted in a new navigation, as the chain | |
430 // length will be included in the aborter PageLoadTracker. | |
431 if (page_end_reason_ != END_RELOAD && | |
432 page_end_reason_ != END_FORWARD_BACK && | |
433 page_end_reason_ != END_NEW_NAVIGATION) { | |
434 LogAbortChainHistograms(nullptr); | |
435 } | |
436 } else if (page_load_metrics::IsEmpty(*merged_page_timing_)) { | |
437 RecordInternalError(ERR_NO_IPCS_RECEIVED); | |
438 } | |
439 | |
440 const PageLoadExtraInfo info = ComputePageLoadExtraInfo(); | |
441 for (const auto& observer : observers_) { | |
442 if (failed_provisional_load_info_) { | |
443 observer->OnFailedProvisionalLoad(*failed_provisional_load_info_, info); | |
444 } else if (did_commit_) { | |
445 observer->OnComplete(*merged_page_timing_, info); | |
446 } | |
447 } | |
448 } | |
449 | |
450 void PageLoadTracker::LogAbortChainHistograms( | |
451 content::NavigationHandle* final_navigation) { | |
452 if (aborted_chain_size_ == 0) | |
453 return; | |
454 // Note that this could be broken out by this navigation's abort type, if more | |
455 // granularity is needed. Add one to the chain size to count the current | |
456 // navigation. In the other cases, the current navigation is the final | |
457 // navigation (which commits). | |
458 if (!final_navigation) { | |
459 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeNoCommit, | |
460 aborted_chain_size_ + 1); | |
461 LogAbortChainSameURLHistogram(aborted_chain_size_same_url_ + 1); | |
462 return; | |
463 } | |
464 | |
465 // The following is only executed for committing trackers. | |
466 DCHECK(did_commit_); | |
467 | |
468 // Note that histograms could be separated out by this commit's transition | |
469 // type, but for simplicity they will all be bucketed together. | |
470 LogAbortChainSameURLHistogram(aborted_chain_size_same_url_); | |
471 | |
472 ui::PageTransition committed_transition = | |
473 final_navigation->GetPageTransition(); | |
474 switch (EndReasonForPageTransition(committed_transition)) { | |
475 case END_RELOAD: | |
476 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeReload, | |
477 aborted_chain_size_); | |
478 return; | |
479 case END_FORWARD_BACK: | |
480 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeForwardBack, | |
481 aborted_chain_size_); | |
482 return; | |
483 // TODO(csharrison): Refactor this code so it is based on the WillStart* | |
484 // code path instead of the committed load code path. Then, for every abort | |
485 // chain, log a histogram of the counts of each of these metrics. For now, | |
486 // merge client redirects with new navigations, which was (basically) the | |
487 // previous behavior. | |
488 case END_CLIENT_REDIRECT: | |
489 case END_NEW_NAVIGATION: | |
490 UMA_HISTOGRAM_COUNTS(internal::kAbortChainSizeNewNavigation, | |
491 aborted_chain_size_); | |
492 return; | |
493 default: | |
494 NOTREACHED() | |
495 << "LogAbortChainHistograms received unexpected ui::PageTransition: " | |
496 << committed_transition; | |
497 return; | |
498 } | |
499 } | |
500 | |
501 void PageLoadTracker::WebContentsHidden() { | |
502 // Only log the first time we background in a given page load. | |
503 if (background_time_.is_null()) { | |
504 // Make sure we either started in the foreground and haven't been | |
505 // foregrounded yet, or started in the background and have already been | |
506 // foregrounded. | |
507 DCHECK_EQ(started_in_foreground_, foreground_time_.is_null()); | |
508 background_time_ = base::TimeTicks::Now(); | |
509 ClampBrowserTimestampIfInterProcessTimeTickSkew(&background_time_); | |
510 } | |
511 const PageLoadExtraInfo info = ComputePageLoadExtraInfo(); | |
512 INVOKE_AND_PRUNE_OBSERVERS(observers_, OnHidden, *merged_page_timing_, info); | |
513 } | |
514 | |
515 void PageLoadTracker::WebContentsShown() { | |
516 // Only log the first time we foreground in a given page load. | |
517 if (foreground_time_.is_null()) { | |
518 // Make sure we either started in the background and haven't been | |
519 // backgrounded yet, or started in the foreground and have already been | |
520 // backgrounded. | |
521 DCHECK_NE(started_in_foreground_, background_time_.is_null()); | |
522 foreground_time_ = base::TimeTicks::Now(); | |
523 ClampBrowserTimestampIfInterProcessTimeTickSkew(&foreground_time_); | |
524 } | |
525 | |
526 INVOKE_AND_PRUNE_OBSERVERS(observers_, OnShown); | |
527 } | |
528 | |
529 void PageLoadTracker::WillProcessNavigationResponse( | |
530 content::NavigationHandle* navigation_handle) { | 264 content::NavigationHandle* navigation_handle) { |
531 DCHECK(!navigation_request_id_.has_value()); | |
532 navigation_request_id_ = navigation_handle->GetGlobalRequestID(); | |
533 DCHECK(navigation_request_id_.value() != content::GlobalRequestID()); | |
534 } | |
535 | |
536 void PageLoadTracker::Commit(content::NavigationHandle* navigation_handle) { | |
537 did_commit_ = true; | |
538 url_ = navigation_handle->GetURL(); | |
539 // Some transitions (like CLIENT_REDIRECT) are only known at commit time. | |
540 page_transition_ = navigation_handle->GetPageTransition(); | |
541 user_initiated_info_.user_gesture = navigation_handle->HasUserGesture(); | |
542 | |
543 INVOKE_AND_PRUNE_OBSERVERS( | |
544 observers_, ShouldObserveMimeType, | |
545 navigation_handle->GetWebContents()->GetContentsMimeType()); | |
546 | |
547 INVOKE_AND_PRUNE_OBSERVERS(observers_, OnCommit, navigation_handle); | |
548 LogAbortChainHistograms(navigation_handle); | |
549 } | |
550 | |
551 void PageLoadTracker::DidCommitSameDocumentNavigation( | |
552 content::NavigationHandle* navigation_handle) { | |
553 for (const auto& observer : observers_) { | |
554 observer->OnCommitSameDocumentNavigation(navigation_handle); | |
555 } | |
556 } | |
557 | |
558 void PageLoadTracker::DidFinishSubFrameNavigation( | |
559 content::NavigationHandle* navigation_handle) { | |
560 for (const auto& observer : observers_) { | |
561 observer->OnDidFinishSubFrameNavigation(navigation_handle); | |
562 } | |
563 | |
564 if (!navigation_handle->HasCommitted()) | 265 if (!navigation_handle->HasCommitted()) |
565 return; | 266 return; |
566 | 267 |
567 // We have a new committed navigation, so discard information about the | 268 // We have a new committed navigation, so discard information about the |
568 // previously committed navigation. | 269 // previously committed navigation. |
569 subframe_navigation_start_offset_.erase( | 270 subframe_navigation_start_offset_.erase( |
570 navigation_handle->GetFrameTreeNodeId()); | 271 navigation_handle->GetFrameTreeNodeId()); |
571 | 272 |
572 BrowserPageTrackDecider decider(embedder_interface_, | 273 BrowserPageTrackDecider decider(embedder_interface_, |
573 navigation_handle->GetWebContents(), | 274 navigation_handle->GetWebContents(), |
574 navigation_handle); | 275 navigation_handle); |
575 if (!decider.ShouldTrack()) | 276 if (!decider.ShouldTrack()) |
576 return; | 277 return; |
577 | 278 |
578 if (navigation_start_ > navigation_handle->NavigationStart()) { | 279 if (navigation_start_ > navigation_handle->NavigationStart()) { |
579 RecordInternalError(ERR_SUBFRAME_NAVIGATION_START_BEFORE_MAIN_FRAME); | 280 RecordInternalError(ERR_SUBFRAME_NAVIGATION_START_BEFORE_MAIN_FRAME); |
580 return; | 281 return; |
581 } | 282 } |
582 base::TimeDelta navigation_delta = | 283 base::TimeDelta navigation_delta = |
583 navigation_handle->NavigationStart() - navigation_start_; | 284 navigation_handle->NavigationStart() - navigation_start_; |
584 subframe_navigation_start_offset_.insert(std::make_pair( | 285 subframe_navigation_start_offset_.insert(std::make_pair( |
585 navigation_handle->GetFrameTreeNodeId(), navigation_delta)); | 286 navigation_handle->GetFrameTreeNodeId(), navigation_delta)); |
586 } | 287 } |
587 | 288 |
588 void PageLoadTracker::FailedProvisionalLoad( | 289 void PageLoadMetricsUpdateDispatcher::UpdateSubFrameTiming( |
589 content::NavigationHandle* navigation_handle, | |
590 base::TimeTicks failed_load_time) { | |
591 DCHECK(!failed_provisional_load_info_); | |
592 failed_provisional_load_info_.reset(new FailedProvisionalLoadInfo( | |
593 failed_load_time - navigation_handle->NavigationStart(), | |
594 navigation_handle->GetNetErrorCode())); | |
595 } | |
596 | |
597 void PageLoadTracker::Redirect(content::NavigationHandle* navigation_handle) { | |
598 url_ = navigation_handle->GetURL(); | |
599 INVOKE_AND_PRUNE_OBSERVERS(observers_, OnRedirect, navigation_handle); | |
600 } | |
601 | |
602 void PageLoadTracker::OnInputEvent(const blink::WebInputEvent& event) { | |
603 input_tracker_.OnInputEvent(event); | |
604 for (const auto& observer : observers_) { | |
605 observer->OnUserInput(event); | |
606 } | |
607 } | |
608 | |
609 void PageLoadTracker::FlushMetricsOnAppEnterBackground() { | |
610 if (!app_entered_background_) { | |
611 RecordAppBackgroundPageLoadCompleted(false); | |
612 app_entered_background_ = true; | |
613 } | |
614 | |
615 const PageLoadExtraInfo info = ComputePageLoadExtraInfo(); | |
616 INVOKE_AND_PRUNE_OBSERVERS(observers_, FlushMetricsOnAppEnterBackground, | |
617 *merged_page_timing_, info); | |
618 } | |
619 | |
620 void PageLoadTracker::NotifyClientRedirectTo( | |
621 const PageLoadTracker& destination) { | |
622 if (merged_page_timing_->paint_timing->first_paint) { | |
623 base::TimeTicks first_paint_time = | |
624 navigation_start() + | |
625 merged_page_timing_->paint_timing->first_paint.value(); | |
626 base::TimeDelta first_paint_to_navigation; | |
627 if (destination.navigation_start() > first_paint_time) | |
628 first_paint_to_navigation = | |
629 destination.navigation_start() - first_paint_time; | |
630 PAGE_LOAD_HISTOGRAM(internal::kClientRedirectFirstPaintToNavigation, | |
631 first_paint_to_navigation); | |
632 } else { | |
633 UMA_HISTOGRAM_BOOLEAN(internal::kClientRedirectWithoutPaint, true); | |
634 } | |
635 } | |
636 | |
637 void PageLoadTracker::UpdateSubFrameTiming( | |
638 content::RenderFrameHost* render_frame_host, | 290 content::RenderFrameHost* render_frame_host, |
639 const mojom::PageLoadTiming& new_timing, | 291 const mojom::PageLoadTiming& new_timing) { |
640 const mojom::PageLoadMetadata& new_metadata) { | |
641 UpdateSubFrameMetadata(new_metadata); | |
642 const auto it = subframe_navigation_start_offset_.find( | 292 const auto it = subframe_navigation_start_offset_.find( |
643 render_frame_host->GetFrameTreeNodeId()); | 293 render_frame_host->GetFrameTreeNodeId()); |
644 if (it == subframe_navigation_start_offset_.end()) { | 294 if (it == subframe_navigation_start_offset_.end()) { |
645 // We received timing information for an untracked load. Ignore it. | 295 // We received timing information for an untracked load. Ignore it. |
646 return; | 296 return; |
647 } | 297 } |
648 | 298 |
649 base::TimeDelta navigation_start_offset = it->second; | 299 base::TimeDelta navigation_start_offset = it->second; |
650 MergePaintTiming(navigation_start_offset, *(new_timing.paint_timing), | 300 MergePaintTiming(navigation_start_offset, *(new_timing.paint_timing), |
651 false /* is_main_frame */); | 301 false /* is_main_frame */); |
652 | 302 |
653 DispatchTimingUpdates(); | 303 DispatchTimingUpdates(); |
654 } | 304 } |
655 | 305 |
656 void PageLoadTracker::MergePaintTiming( | 306 void PageLoadMetricsUpdateDispatcher::MergePaintTiming( |
657 base::TimeDelta navigation_start_offset, | 307 base::TimeDelta navigation_start_offset, |
658 const mojom::PaintTiming& new_paint_timing, | 308 const mojom::PaintTiming& new_paint_timing, |
659 bool is_main_frame) { | 309 bool is_main_frame) { |
660 MaybeUpdateTimeDelta(&merged_page_timing_->paint_timing->first_paint, | 310 MaybeUpdateTimeDelta(&pending_merged_page_timing_->paint_timing->first_paint, |
661 navigation_start_offset, new_paint_timing.first_paint); | 311 navigation_start_offset, new_paint_timing.first_paint); |
662 MaybeUpdateTimeDelta(&merged_page_timing_->paint_timing->first_text_paint, | |
663 navigation_start_offset, | |
664 new_paint_timing.first_text_paint); | |
665 MaybeUpdateTimeDelta(&merged_page_timing_->paint_timing->first_image_paint, | |
666 navigation_start_offset, | |
667 new_paint_timing.first_image_paint); | |
668 MaybeUpdateTimeDelta( | 312 MaybeUpdateTimeDelta( |
669 &merged_page_timing_->paint_timing->first_contentful_paint, | 313 &pending_merged_page_timing_->paint_timing->first_text_paint, |
314 navigation_start_offset, new_paint_timing.first_text_paint); | |
315 MaybeUpdateTimeDelta( | |
316 &pending_merged_page_timing_->paint_timing->first_image_paint, | |
317 navigation_start_offset, new_paint_timing.first_image_paint); | |
318 MaybeUpdateTimeDelta( | |
319 &pending_merged_page_timing_->paint_timing->first_contentful_paint, | |
670 navigation_start_offset, new_paint_timing.first_contentful_paint); | 320 navigation_start_offset, new_paint_timing.first_contentful_paint); |
671 if (is_main_frame) { | 321 if (is_main_frame) { |
672 // first meaningful paint is only tracked in the main frame. | 322 // first meaningful paint is only tracked in the main frame. |
673 merged_page_timing_->paint_timing->first_meaningful_paint = | 323 pending_merged_page_timing_->paint_timing->first_meaningful_paint = |
674 new_paint_timing.first_meaningful_paint; | 324 new_paint_timing.first_meaningful_paint; |
675 } | 325 } |
676 } | 326 } |
677 | 327 |
678 void PageLoadTracker::UpdateSubFrameMetadata( | 328 void PageLoadMetricsUpdateDispatcher::UpdateSubFrameMetadata( |
679 const mojom::PageLoadMetadata& subframe_metadata) { | 329 const mojom::PageLoadMetadata& subframe_metadata) { |
680 // Merge the subframe loading behavior flags with any we've already observed, | 330 // Merge the subframe loading behavior flags with any we've already observed, |
681 // possibly from other subframes. | 331 // possibly from other subframes. |
682 const int last_subframe_loading_behavior_flags = | 332 const int last_subframe_loading_behavior_flags = |
683 subframe_metadata_.behavior_flags; | 333 subframe_metadata_->behavior_flags; |
684 subframe_metadata_.behavior_flags |= subframe_metadata.behavior_flags; | 334 subframe_metadata_->behavior_flags |= subframe_metadata.behavior_flags; |
685 if (last_subframe_loading_behavior_flags == subframe_metadata_.behavior_flags) | 335 if (last_subframe_loading_behavior_flags == |
336 subframe_metadata_->behavior_flags) | |
686 return; | 337 return; |
687 | 338 |
688 PageLoadExtraInfo extra_info(ComputePageLoadExtraInfo()); | 339 client_->OnSubframeMetadataChanged(); |
689 for (const auto& observer : observers_) { | |
690 observer->OnLoadingBehaviorObserved(extra_info); | |
691 } | |
692 } | 340 } |
693 | 341 |
694 void PageLoadTracker::UpdateTiming( | 342 void PageLoadMetricsUpdateDispatcher::UpdateMainFrameTiming( |
695 const mojom::PageLoadTiming& new_timing, | 343 const mojom::PageLoadTiming& new_timing) { |
696 const mojom::PageLoadMetadata& new_metadata) { | |
697 // Throw away IPCs that are not relevant to the current navigation. | 344 // Throw away IPCs that are not relevant to the current navigation. |
698 // Two timing structures cannot refer to the same navigation if they indicate | 345 // Two timing structures cannot refer to the same navigation if they indicate |
699 // that a navigation started at different times, so a new timing struct with a | 346 // that a navigation started at different times, so a new timing struct with a |
700 // different start time from an earlier struct is considered invalid. | 347 // different start time from an earlier struct is considered invalid. |
701 const bool valid_timing_descendent = | 348 const bool valid_timing_descendent = |
702 merged_page_timing_->navigation_start.is_null() || | 349 pending_merged_page_timing_->navigation_start.is_null() || |
703 merged_page_timing_->navigation_start == new_timing.navigation_start; | 350 pending_merged_page_timing_->navigation_start == |
351 new_timing.navigation_start; | |
704 if (!valid_timing_descendent) { | 352 if (!valid_timing_descendent) { |
705 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING_DESCENDENT); | 353 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING_DESCENDENT); |
706 return; | 354 return; |
707 } | 355 } |
708 | 356 |
709 // Ensure flags sent previously are still present in the new metadata fields. | |
710 const bool valid_behavior_descendent = | |
711 (main_frame_metadata_.behavior_flags & new_metadata.behavior_flags) == | |
712 main_frame_metadata_.behavior_flags; | |
713 if (!valid_behavior_descendent) { | |
714 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT); | |
715 return; | |
716 } | |
717 internal::PageLoadTimingStatus status = IsValidPageLoadTiming(new_timing); | 357 internal::PageLoadTimingStatus status = IsValidPageLoadTiming(new_timing); |
718 UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingStatus, status, | 358 UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingStatus, status, |
719 internal::LAST_PAGE_LOAD_TIMING_STATUS); | 359 internal::LAST_PAGE_LOAD_TIMING_STATUS); |
720 if (status != internal::VALID) { | 360 if (status != internal::VALID) { |
721 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING); | 361 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_TIMING); |
722 return; | 362 return; |
723 } | 363 } |
724 | 364 |
725 DCHECK(did_commit_); // OnCommit() must be called first. | 365 mojom::PaintTimingPtr last_paint_timing = |
726 // There are some subtle ordering constraints here. GetPageLoadMetricsInfo() | 366 std::move(pending_merged_page_timing_->paint_timing); |
727 // must be called before DispatchObserverTimingCallbacks, but its | 367 // Update the pending_merged_page_timing_, making sure to merge the previously |
728 // implementation depends on the state of main_frame_metadata_, so we need | |
729 // to update main_frame_metadata_ before calling GetPageLoadMetricsInfo. | |
730 // Thus, we make a copy of timing here, update merged_page_timing_ and | |
731 // main_frame_metadata_, and then proceed to dispatch the observer timing | |
732 // callbacks. | |
733 const mojom::PaintTimingPtr last_paint_timing = | |
734 std::move(merged_page_timing_->paint_timing); | |
735 | |
736 // Update the merged_page_timing_, making sure to merge the previously | |
737 // observed |paint_timing|, which is tracked across all frames in the page. | 368 // observed |paint_timing|, which is tracked across all frames in the page. |
738 merged_page_timing_ = new_timing.Clone(); | 369 pending_merged_page_timing_ = new_timing.Clone(); |
739 merged_page_timing_->paint_timing = last_paint_timing.Clone(); | 370 pending_merged_page_timing_->paint_timing = std::move(last_paint_timing); |
740 MergePaintTiming(base::TimeDelta(), *new_timing.paint_timing, | 371 MergePaintTiming(base::TimeDelta(), *new_timing.paint_timing, |
741 true /* is_main_frame */); | 372 true /* is_main_frame */); |
742 | 373 |
743 main_frame_metadata_ = new_metadata; | |
744 | |
745 DispatchTimingUpdates(); | 374 DispatchTimingUpdates(); |
746 } | 375 } |
747 | 376 |
748 void PageLoadTracker::DispatchTimingUpdates() { | 377 void PageLoadMetricsUpdateDispatcher::UpdateMainFrameMetadata( |
749 if (last_dispatched_merged_page_timing_->Equals(*merged_page_timing_) && | 378 const mojom::PageLoadMetadata& new_metadata) { |
750 last_dispatched_main_frame_metadata_->Equals(main_frame_metadata_)) { | 379 if (main_frame_metadata_->Equals(new_metadata)) |
380 return; | |
381 | |
382 // Ensure flags sent previously are still present in the new metadata fields. | |
383 const bool valid_behavior_descendent = | |
384 (main_frame_metadata_->behavior_flags & new_metadata.behavior_flags) == | |
385 main_frame_metadata_->behavior_flags; | |
386 if (!valid_behavior_descendent) { | |
387 RecordInternalError(ERR_BAD_TIMING_IPC_INVALID_BEHAVIOR_DESCENDENT); | |
751 return; | 388 return; |
752 } | 389 } |
753 | 390 |
754 if (merged_page_timing_->paint_timing->first_paint) { | 391 main_frame_metadata_ = new_metadata.Clone(); |
755 if (!merged_page_timing_->parse_timing->parse_start || | 392 client_->OnMainFrameMetadataChanged(); |
756 !merged_page_timing_->document_timing->first_layout) { | 393 } |
394 | |
395 void PageLoadMetricsUpdateDispatcher::DispatchTimingUpdates() { | |
396 if (pending_merged_page_timing_->paint_timing->first_paint) { | |
397 if (!pending_merged_page_timing_->parse_timing->parse_start || | |
398 !pending_merged_page_timing_->document_timing->first_layout) { | |
757 // When merging paint events across frames, we can sometimes encounter | 399 // When merging paint events across frames, we can sometimes encounter |
758 // cases where we've received a first paint event for a child frame before | 400 // cases where we've received a first paint event for a child frame before |
759 // receiving required earlier events in the main frame, due to buffering | 401 // receiving required earlier events in the main frame, due to buffering |
760 // in the render process which results in out of order delivery. For | 402 // in the render process which results in out of order delivery. For |
761 // example, we may receive a notification for a first paint in a child | 403 // example, we may receive a notification for a first paint in a child |
762 // frame before we've received a notification for parse start or first | 404 // frame before we've received a notification for parse start or first |
763 // layout in the main frame. In these cases, we delay sending timing | 405 // layout in the main frame. In these cases, we delay sending timing |
764 // updates until we've received all expected events (e.g. wait to receive | 406 // updates until we've received all expected events (e.g. wait to receive |
765 // a parse or layout event before dispatching a paint event), so observers | 407 // a parse or layout event before dispatching a paint event), so observers |
766 // can make assumptions about ordering of these events in their callbacks. | 408 // can make assumptions about ordering of these events in their callbacks. |
767 return; | 409 return; |
768 } | 410 } |
769 } | 411 } |
770 | 412 |
413 if (current_merged_page_timing_->Equals(*pending_merged_page_timing_)) | |
414 return; | |
415 current_merged_page_timing_ = pending_merged_page_timing_->Clone(); | |
416 | |
771 internal::PageLoadTimingStatus status = | 417 internal::PageLoadTimingStatus status = |
772 IsValidPageLoadTiming(*merged_page_timing_); | 418 IsValidPageLoadTiming(*pending_merged_page_timing_); |
773 UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingDispatchStatus, status, | 419 UMA_HISTOGRAM_ENUMERATION(internal::kPageLoadTimingDispatchStatus, status, |
774 internal::LAST_PAGE_LOAD_TIMING_STATUS); | 420 internal::LAST_PAGE_LOAD_TIMING_STATUS); |
775 | 421 |
776 const PageLoadExtraInfo info = ComputePageLoadExtraInfo(); | 422 client_->OnTimingChanged(); |
777 for (const auto& observer : observers_) { | |
778 DispatchObserverTimingCallbacks( | |
779 observer.get(), *last_dispatched_merged_page_timing_, | |
780 *merged_page_timing_, *last_dispatched_main_frame_metadata_, info); | |
781 } | |
782 last_dispatched_merged_page_timing_ = merged_page_timing_->Clone(); | |
783 last_dispatched_main_frame_metadata_ = main_frame_metadata_.Clone(); | |
784 } | |
785 | |
786 void PageLoadTracker::OnStartedResource( | |
787 const ExtraRequestStartInfo& extra_request_start_info) { | |
788 for (const auto& observer : observers_) { | |
789 observer->OnStartedResource(extra_request_start_info); | |
790 } | |
791 } | |
792 | |
793 void PageLoadTracker::OnLoadedResource( | |
794 const ExtraRequestCompleteInfo& extra_request_complete_info) { | |
795 for (const auto& observer : observers_) { | |
796 observer->OnLoadedResource(extra_request_complete_info); | |
797 } | |
798 } | |
799 | |
800 void PageLoadTracker::StopTracking() { | |
801 did_stop_tracking_ = true; | |
802 observers_.clear(); | |
803 } | |
804 | |
805 void PageLoadTracker::AddObserver( | |
806 std::unique_ptr<PageLoadMetricsObserver> observer) { | |
807 observers_.push_back(std::move(observer)); | |
808 } | |
809 | |
810 void PageLoadTracker::ClampBrowserTimestampIfInterProcessTimeTickSkew( | |
811 base::TimeTicks* event_time) { | |
812 DCHECK(event_time != nullptr); | |
813 // Windows 10 GCE bot non-deterministically failed because TimeTicks::Now() | |
814 // called in the browser process e.g. commit_time was less than | |
815 // navigation_start_ that was populated in the renderer process because the | |
816 // clock was not system-wide monotonic. | |
817 // Note that navigation_start_ can also be set in the browser process in | |
818 // some cases and in those cases event_time should never be < | |
819 // navigation_start_. If it is due to a code error and it gets clamped in this | |
820 // function, on high resolution systems it should lead to a dcheck failure. | |
821 | |
822 // TODO(shivanisha): Currently IsHighResolution is the best way to check | |
823 // if the clock is system-wide monotonic. However IsHighResolution | |
824 // does a broader check to see if the clock in use is high resolution | |
825 // which also implies it is system-wide monotonic (on Windows). | |
826 if (base::TimeTicks::IsHighResolution()) { | |
827 DCHECK(event_time->is_null() || *event_time >= navigation_start_); | |
828 return; | |
829 } | |
830 | |
831 if (!event_time->is_null() && *event_time < navigation_start_) { | |
832 RecordInternalError(ERR_INTER_PROCESS_TIME_TICK_SKEW); | |
833 *event_time = navigation_start_; | |
834 } | |
835 } | |
836 | |
837 PageLoadExtraInfo PageLoadTracker::ComputePageLoadExtraInfo() { | |
838 base::Optional<base::TimeDelta> first_background_time; | |
839 base::Optional<base::TimeDelta> first_foreground_time; | |
840 base::Optional<base::TimeDelta> page_end_time; | |
841 | |
842 if (!background_time_.is_null()) { | |
843 DCHECK_GE(background_time_, navigation_start_); | |
844 first_background_time = background_time_ - navigation_start_; | |
845 } | |
846 | |
847 if (!foreground_time_.is_null()) { | |
848 DCHECK_GE(foreground_time_, navigation_start_); | |
849 first_foreground_time = foreground_time_ - navigation_start_; | |
850 } | |
851 | |
852 if (page_end_reason_ != END_NONE) { | |
853 DCHECK_GE(page_end_time_, navigation_start_); | |
854 page_end_time = page_end_time_ - navigation_start_; | |
855 } else { | |
856 DCHECK(page_end_time_.is_null()); | |
857 } | |
858 | |
859 // page_end_reason_ == END_NONE implies page_end_user_initiated_info_ is not | |
860 // user initiated. | |
861 DCHECK(page_end_reason_ != END_NONE || | |
862 (!page_end_user_initiated_info_.browser_initiated && | |
863 !page_end_user_initiated_info_.user_gesture && | |
864 !page_end_user_initiated_info_.user_input_event)); | |
865 return PageLoadExtraInfo( | |
866 navigation_start_, first_background_time, first_foreground_time, | |
867 started_in_foreground_, user_initiated_info_, url(), start_url_, | |
868 did_commit_, page_end_reason_, page_end_user_initiated_info_, | |
869 page_end_time, main_frame_metadata_, subframe_metadata_); | |
870 } | |
871 | |
872 bool PageLoadTracker::HasMatchingNavigationRequestID( | |
873 const content::GlobalRequestID& request_id) const { | |
874 DCHECK(request_id != content::GlobalRequestID()); | |
875 return navigation_request_id_.has_value() && | |
876 navigation_request_id_.value() == request_id; | |
877 } | |
878 | |
879 void PageLoadTracker::NotifyPageEnd(PageEndReason page_end_reason, | |
880 UserInitiatedInfo user_initiated_info, | |
881 base::TimeTicks timestamp, | |
882 bool is_certainly_browser_timestamp) { | |
883 DCHECK_NE(page_end_reason, END_NONE); | |
884 // Use UpdatePageEnd to update an already notified PageLoadTracker. | |
885 if (page_end_reason_ != END_NONE) | |
886 return; | |
887 | |
888 UpdatePageEndInternal(page_end_reason, user_initiated_info, timestamp, | |
889 is_certainly_browser_timestamp); | |
890 } | |
891 | |
892 void PageLoadTracker::UpdatePageEnd(PageEndReason page_end_reason, | |
893 UserInitiatedInfo user_initiated_info, | |
894 base::TimeTicks timestamp, | |
895 bool is_certainly_browser_timestamp) { | |
896 DCHECK_NE(page_end_reason, END_NONE); | |
897 DCHECK_NE(page_end_reason, END_OTHER); | |
898 DCHECK_EQ(page_end_reason_, END_OTHER); | |
899 DCHECK(!page_end_time_.is_null()); | |
900 if (page_end_time_.is_null() || page_end_reason_ != END_OTHER) | |
901 return; | |
902 | |
903 // For some aborts (e.g. navigations), the initiated timestamp can be earlier | |
904 // than the timestamp that aborted the load. Taking the minimum gives the | |
905 // closest user initiated time known. | |
906 UpdatePageEndInternal(page_end_reason, user_initiated_info, | |
907 std::min(page_end_time_, timestamp), | |
908 is_certainly_browser_timestamp); | |
909 } | |
910 | |
911 bool PageLoadTracker::IsLikelyProvisionalAbort( | |
912 base::TimeTicks abort_cause_time) const { | |
913 // Note that |abort_cause_time - page_end_time_| can be negative. | |
914 return page_end_reason_ == END_OTHER && | |
915 (abort_cause_time - page_end_time_).InMilliseconds() < 100; | |
916 } | |
917 | |
918 bool PageLoadTracker::MatchesOriginalNavigation( | |
919 content::NavigationHandle* navigation_handle) { | |
920 // Neither navigation should have committed. | |
921 DCHECK(!navigation_handle->HasCommitted()); | |
922 DCHECK(!did_commit_); | |
923 return navigation_handle->GetURL() == start_url_; | |
924 } | |
925 | |
926 void PageLoadTracker::UpdatePageEndInternal( | |
927 PageEndReason page_end_reason, | |
928 UserInitiatedInfo user_initiated_info, | |
929 base::TimeTicks timestamp, | |
930 bool is_certainly_browser_timestamp) { | |
931 // When a provisional navigation commits, that navigation's start time is | |
932 // interpreted as the abort time for other provisional loads in the tab. | |
933 // However, this only makes sense if the committed load started after the | |
934 // aborted provisional loads started. Thus we ignore cases where the committed | |
935 // load started before the aborted provisional load, as this would result in | |
936 // recording a negative time-to-abort. The real issue here is that we have to | |
937 // infer the cause of aborts. It would be better if the navigation code could | |
938 // instead report the actual cause of an aborted navigation. See crbug/571647 | |
939 // for details. | |
940 if (timestamp < navigation_start_) { | |
941 RecordInternalError(ERR_END_BEFORE_NAVIGATION_START); | |
942 page_end_reason_ = END_NONE; | |
943 page_end_time_ = base::TimeTicks(); | |
944 return; | |
945 } | |
946 page_end_reason_ = page_end_reason; | |
947 page_end_time_ = timestamp; | |
948 // A client redirect can never be user initiated. Due to the way Blink | |
949 // implements user gesture tracking, where all events that occur within 1 | |
950 // second after a user interaction are considered to be triggered by user | |
951 // activation (based on HTML spec: | |
952 // https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-a ctivation), | |
953 // these navs may sometimes be reported as user initiated by Blink. Thus, we | |
954 // explicitly filter these types of aborts out when deciding if the abort was | |
955 // user initiated. | |
956 if (page_end_reason != END_CLIENT_REDIRECT) | |
957 page_end_user_initiated_info_ = user_initiated_info; | |
958 | |
959 if (is_certainly_browser_timestamp) { | |
960 ClampBrowserTimestampIfInterProcessTimeTickSkew(&page_end_time_); | |
961 } | |
962 } | |
963 | |
964 void PageLoadTracker::MediaStartedPlaying( | |
965 const content::WebContentsObserver::MediaPlayerInfo& video_type, | |
966 bool is_in_main_frame) { | |
967 for (const auto& observer : observers_) | |
968 observer->MediaStartedPlaying(video_type, is_in_main_frame); | |
969 } | |
970 | |
971 void PageLoadTracker::OnNavigationDelayComplete(base::TimeDelta scheduled_delay, | |
972 base::TimeDelta actual_delay) { | |
973 for (const auto& observer : observers_) | |
974 observer->OnNavigationDelayComplete(scheduled_delay, actual_delay); | |
975 } | 423 } |
976 | 424 |
977 } // namespace page_load_metrics | 425 } // namespace page_load_metrics |
OLD | NEW |