1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/page_load_metrics/browser/page_load_metrics_util.h"
6
7 #include <algorithm>
8
9 #include "components/page_load_metrics/common/page_load_timing.h"
10
11 namespace page_load_metrics {
12
13 namespace {
14
IsBackgroundAbort(const PageLoadMetricsObserverDelegate & delegate)15 bool IsBackgroundAbort(const PageLoadMetricsObserverDelegate& delegate) {
16 if (!delegate.StartedInForeground() || !delegate.GetFirstBackgroundTime())
17 return false;
18
19 if (!delegate.GetPageEndTime())
20 return true;
21
22 return delegate.GetFirstBackgroundTime() <= delegate.GetPageEndTime();
23 }
24
GetAbortReasonForEndReason(PageEndReason end_reason)25 PageAbortReason GetAbortReasonForEndReason(PageEndReason end_reason) {
26 switch (end_reason) {
27 case END_RELOAD:
28 return ABORT_RELOAD;
29 case END_FORWARD_BACK:
30 return ABORT_FORWARD_BACK;
31 case END_NEW_NAVIGATION:
32 return ABORT_NEW_NAVIGATION;
33 case END_STOP:
34 return ABORT_STOP;
35 case END_CLOSE:
36 return ABORT_CLOSE;
37 case END_OTHER:
38 return ABORT_OTHER;
39 default:
40 return ABORT_NONE;
41 }
42 }
43
44 // Common helper for QueryContainsComponent and QueryContainsComponentPrefix.
QueryContainsComponentHelper(const base::StringPiece query,const base::StringPiece component,bool component_is_prefix)45 bool QueryContainsComponentHelper(const base::StringPiece query,
46 const base::StringPiece component,
47 bool component_is_prefix) {
48 if (query.empty() || component.empty() ||
49 component.length() > query.length()) {
50 return false;
51 }
52
53 // Ensures that the first character of |query| is not a query or fragment
54 // delimiter character (? or #). Including it can break the later test for
55 // |component| being at the start of the query string.
56 // Note: This heuristic can cause a component string that starts with one of
57 // these characters to not match a query string which contains it at the
58 // beginning.
59 const base::StringPiece trimmed_query =
60 base::TrimString(query, "?#", base::TrimPositions::TRIM_LEADING);
61
62 // We shouldn't try to find matches beyond the point where there aren't enough
63 // characters left in query to fully match the component.
64 const size_t last_search_start = trimmed_query.length() - component.length();
65
66 // We need to search for matches in a loop, rather than stopping at the first
67 // match, because we may initially match a substring that isn't a full query
68 // string component. Consider, for instance, the query string 'ab=cd&b=c'. If
69 // we search for component 'b=c', the first substring match will be characters
70 // 1-3 (zero-based) in the query string. However, this isn't a full component
71 // (the full component is ab=cd) so the match will fail. Thus, we must
72 // continue our search to find the second substring match, which in the
73 // example is at characters 6-8 (the end of the query string) and is a
74 // successful component match.
75 for (size_t start_offset = 0; start_offset <= last_search_start;
76 start_offset += component.length()) {
77 start_offset = trimmed_query.find(component, start_offset);
78 if (start_offset == std::string::npos) {
79 // We searched to end of string and did not find a match.
80 return false;
81 }
82 // Verify that the character prior to the component is valid (either we're
83 // at the beginning of the query string, or are preceded by an ampersand).
84 if (start_offset != 0 && trimmed_query[start_offset - 1] != '&') {
85 continue;
86 }
87 if (!component_is_prefix) {
88 // Verify that the character after the component substring is valid
89 // (either we're at the end of the query string, or are followed by an
90 // ampersand).
91 const size_t after_offset = start_offset + component.length();
92 if (after_offset < trimmed_query.length() &&
93 trimmed_query[after_offset] != '&') {
94 continue;
95 }
96 }
97 return true;
98 }
99 return false;
100 }
101
102 } // namespace
103
WasStartedInForegroundOptionalEventInForeground(const base::Optional<base::TimeDelta> & event,const PageLoadMetricsObserverDelegate & delegate)104 bool WasStartedInForegroundOptionalEventInForeground(
105 const base::Optional<base::TimeDelta>& event,
106 const PageLoadMetricsObserverDelegate& delegate) {
107 return delegate.StartedInForeground() && event &&
108 (!delegate.GetFirstBackgroundTime() ||
109 event.value() <= delegate.GetFirstBackgroundTime().value());
110 }
111
WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore(const base::Optional<base::TimeDelta> & event,const PageLoadMetricsObserverDelegate & delegate,size_t index)112 bool WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore(
113 const base::Optional<base::TimeDelta>& event,
114 const PageLoadMetricsObserverDelegate& delegate,
115 size_t index) {
116 const auto& back_forward_cache_restore =
117 delegate.GetBackForwardCacheRestore(index);
118 base::Optional<base::TimeDelta> first_background_time =
119 back_forward_cache_restore.first_background_time;
120 return back_forward_cache_restore.was_in_foreground && event &&
121 (!first_background_time ||
122 event.value() <= first_background_time.value());
123 }
124
WasStartedInBackgroundOptionalEventInForeground(const base::Optional<base::TimeDelta> & event,const PageLoadMetricsObserverDelegate & delegate)125 bool WasStartedInBackgroundOptionalEventInForeground(
126 const base::Optional<base::TimeDelta>& event,
127 const PageLoadMetricsObserverDelegate& delegate) {
128 return !delegate.StartedInForeground() && event &&
129 delegate.GetFirstForegroundTime() &&
130 delegate.GetFirstForegroundTime().value() <= event.value() &&
131 (!delegate.GetFirstBackgroundTime() ||
132 event.value() <= delegate.GetFirstBackgroundTime().value());
133 }
134
WasInForeground(const PageLoadMetricsObserverDelegate & delegate)135 bool WasInForeground(const PageLoadMetricsObserverDelegate& delegate) {
136 return delegate.StartedInForeground() || delegate.GetFirstForegroundTime();
137 }
138
GetPageAbortInfo(const PageLoadMetricsObserverDelegate & delegate)139 PageAbortInfo GetPageAbortInfo(
140 const PageLoadMetricsObserverDelegate& delegate) {
141 if (IsBackgroundAbort(delegate)) {
142 // Though most cases where a tab is backgrounded are user initiated, we
143 // can't be certain that we were backgrounded due to a user action. For
144 // example, on Android, the screen times out after a period of inactivity,
145 // resulting in a non-user-initiated backgrounding.
146 return {ABORT_BACKGROUND, UserInitiatedInfo::NotUserInitiated(),
147 delegate.GetFirstBackgroundTime().value()};
148 }
149
150 PageAbortReason abort_reason =
151 GetAbortReasonForEndReason(delegate.GetPageEndReason());
152 if (abort_reason == ABORT_NONE)
153 return PageAbortInfo();
154
155 return {abort_reason, delegate.GetPageEndUserInitiatedInfo(),
156 delegate.GetPageEndTime().value()};
157 }
158
GetInitialForegroundDuration(const PageLoadMetricsObserverDelegate & delegate,base::TimeTicks app_background_time)159 base::Optional<base::TimeDelta> GetInitialForegroundDuration(
160 const PageLoadMetricsObserverDelegate& delegate,
161 base::TimeTicks app_background_time) {
162 if (!delegate.StartedInForeground())
163 return base::Optional<base::TimeDelta>();
164
165 base::Optional<base::TimeDelta> time_on_page =
166 OptionalMin(delegate.GetFirstBackgroundTime(), delegate.GetPageEndTime());
167
168 // If we don't have a time_on_page value yet, and we have an app background
169 // time, use the app background time as our end time. This addresses cases
170 // where the Chrome app is backgrounded before the page load is complete, on
171 // platforms where Chrome may be killed once it goes into the background
172 // (Android). In these cases, we use the app background time as the 'end
173 // time'.
174 if (!time_on_page && !app_background_time.is_null()) {
175 time_on_page = app_background_time - delegate.GetNavigationStart();
176 }
177 return time_on_page;
178 }
179
DidObserveLoadingBehaviorInAnyFrame(const PageLoadMetricsObserverDelegate & delegate,blink::LoadingBehaviorFlag behavior)180 bool DidObserveLoadingBehaviorInAnyFrame(
181 const PageLoadMetricsObserverDelegate& delegate,
182 blink::LoadingBehaviorFlag behavior) {
183 const int all_frame_loading_behavior_flags =
184 delegate.GetMainFrameMetadata().behavior_flags |
185 delegate.GetSubframeMetadata().behavior_flags;
186
187 return (all_frame_loading_behavior_flags & behavior) != 0;
188 }
189
IsGoogleSearchHostname(const GURL & url)190 bool IsGoogleSearchHostname(const GURL& url) {
191 base::Optional<std::string> result =
192 page_load_metrics::GetGoogleHostnamePrefix(url);
193 return result && result.value() == "www";
194 }
195
IsGoogleSearchResultUrl(const GURL & url)196 bool IsGoogleSearchResultUrl(const GURL& url) {
197 // NOTE: we do not require 'q=' in the query, as AJAXy search may instead
198 // store the query in the URL fragment.
199 if (!IsGoogleSearchHostname(url)) {
200 return false;
201 }
202
203 if (!QueryContainsComponentPrefix(url.query_piece(), "q=") &&
204 !QueryContainsComponentPrefix(url.ref_piece(), "q=")) {
205 return false;
206 }
207
208 const base::StringPiece path = url.path_piece();
209 return path == "/search" || path == "/webhp" || path == "/custom" ||
210 path == "/";
211 }
212
IsGoogleSearchRedirectorUrl(const GURL & url)213 bool IsGoogleSearchRedirectorUrl(const GURL& url) {
214 if (!IsGoogleSearchHostname(url))
215 return false;
216
217 // The primary search redirector. Google search result redirects are
218 // differentiated from other general google redirects by 'source=web' in the
219 // query string.
220 if (url.path_piece() == "/url" && url.has_query() &&
221 QueryContainsComponent(url.query_piece(), "source=web")) {
222 return true;
223 }
224
225 // Intent-based navigations from search are redirected through a second
226 // redirector, which receives its redirect URL in the fragment/hash/ref
227 // portion of the URL (the portion after '#'). We don't check for the presence
228 // of certain params in the ref since this redirector is only used for
229 // redirects from search.
230 return url.path_piece() == "/searchurl/r.html" && url.has_ref();
231 }
232
QueryContainsComponent(const base::StringPiece query,const base::StringPiece component)233 bool QueryContainsComponent(const base::StringPiece query,
234 const base::StringPiece component) {
235 return QueryContainsComponentHelper(query, component, false);
236 }
237
QueryContainsComponentPrefix(const base::StringPiece query,const base::StringPiece component)238 bool QueryContainsComponentPrefix(const base::StringPiece query,
239 const base::StringPiece component) {
240 return QueryContainsComponentHelper(query, component, true);
241 }
242
LayoutShiftUkmValue(float shift_score)243 int64_t LayoutShiftUkmValue(float shift_score) {
244 // Report (shift_score * 100) as an int in the range [0, 1000].
245 return static_cast<int>(roundf(std::min(shift_score, 10.0f) * 100.0f));
246 }
247
LayoutShiftUmaValue(float shift_score)248 int32_t LayoutShiftUmaValue(float shift_score) {
249 // Report (shift_score * 10) as an int in the range [0, 100].
250 return static_cast<int>(roundf(std::min(shift_score, 10.0f) * 10.0f));
251 }
252
253 } // namespace page_load_metrics
254