| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 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/observers/from_gws_page_load_metrics_
observer.h" | 5 #include "chrome/browser/page_load_metrics/observers/from_gws_page_load_metrics_
observer.h" |
| 6 #include <string> | 6 #include <string> |
| 7 | 7 |
| 8 #include "base/metrics/histogram_macros.h" | 8 #include "base/metrics/histogram_macros.h" |
| 9 #include "base/strings/string_util.h" | 9 #include "base/strings/string_util.h" |
| 10 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" | 10 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h" |
| (...skipping 298 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 309 return time_to_interaction.value() + | 309 return time_to_interaction.value() + |
| 310 base::TimeDelta::FromMilliseconds(1000) > | 310 base::TimeDelta::FromMilliseconds(1000) > |
| 311 abort_info.time_to_abort; | 311 abort_info.time_to_abort; |
| 312 } else { | 312 } else { |
| 313 return time_to_interaction > abort_info.time_to_abort; | 313 return time_to_interaction > abort_info.time_to_abort; |
| 314 } | 314 } |
| 315 } | 315 } |
| 316 | 316 |
| 317 } // namespace | 317 } // namespace |
| 318 | 318 |
| 319 // See | |
| 320 // https://docs.google.com/document/d/1jNPZ6Aeh0KV6umw1yZrrkfXRfxWNruwu7FELLx_cp
Og/edit | |
| 321 // for additional details. | |
| 322 | |
| 323 // static | |
| 324 bool FromGWSPageLoadMetricsLogger::IsGoogleSearchHostname(const GURL& url) { | |
| 325 base::Optional<std::string> result = | |
| 326 page_load_metrics::GetGoogleHostnamePrefix(url); | |
| 327 return result && result.value() == "www"; | |
| 328 } | |
| 329 | |
| 330 // static | |
| 331 bool FromGWSPageLoadMetricsLogger::IsGoogleSearchResultUrl(const GURL& url) { | |
| 332 // NOTE: we do not require 'q=' in the query, as AJAXy search may instead | |
| 333 // store the query in the URL fragment. | |
| 334 if (!IsGoogleSearchHostname(url)) { | |
| 335 return false; | |
| 336 } | |
| 337 | |
| 338 if (!QueryContainsComponentPrefix(url.query_piece(), "q=") && | |
| 339 !QueryContainsComponentPrefix(url.ref_piece(), "q=")) { | |
| 340 return false; | |
| 341 } | |
| 342 | |
| 343 const base::StringPiece path = url.path_piece(); | |
| 344 return path == "/search" || path == "/webhp" || path == "/custom" || | |
| 345 path == "/"; | |
| 346 } | |
| 347 | |
| 348 // static | |
| 349 bool FromGWSPageLoadMetricsLogger::IsGoogleSearchRedirectorUrl( | |
| 350 const GURL& url) { | |
| 351 if (!IsGoogleSearchHostname(url)) | |
| 352 return false; | |
| 353 | |
| 354 // The primary search redirector. Google search result redirects are | |
| 355 // differentiated from other general google redirects by 'source=web' in the | |
| 356 // query string. | |
| 357 if (url.path_piece() == "/url" && url.has_query() && | |
| 358 QueryContainsComponent(url.query_piece(), "source=web")) { | |
| 359 return true; | |
| 360 } | |
| 361 | |
| 362 // Intent-based navigations from search are redirected through a second | |
| 363 // redirector, which receives its redirect URL in the fragment/hash/ref | |
| 364 // portion of the URL (the portion after '#'). We don't check for the presence | |
| 365 // of certain params in the ref since this redirector is only used for | |
| 366 // redirects from search. | |
| 367 return url.path_piece() == "/searchurl/r.html" && url.has_ref(); | |
| 368 } | |
| 369 | |
| 370 // static | |
| 371 bool FromGWSPageLoadMetricsLogger::QueryContainsComponent( | |
| 372 const base::StringPiece query, | |
| 373 const base::StringPiece component) { | |
| 374 return QueryContainsComponentHelper(query, component, false); | |
| 375 } | |
| 376 | |
| 377 // static | |
| 378 bool FromGWSPageLoadMetricsLogger::QueryContainsComponentPrefix( | |
| 379 const base::StringPiece query, | |
| 380 const base::StringPiece component) { | |
| 381 return QueryContainsComponentHelper(query, component, true); | |
| 382 } | |
| 383 | |
| 384 // static | |
| 385 bool FromGWSPageLoadMetricsLogger::QueryContainsComponentHelper( | |
| 386 const base::StringPiece query, | |
| 387 const base::StringPiece component, | |
| 388 bool component_is_prefix) { | |
| 389 if (query.empty() || component.empty() || | |
| 390 component.length() > query.length()) { | |
| 391 return false; | |
| 392 } | |
| 393 | |
| 394 // Verify that the provided query string does not include the query or | |
| 395 // fragment start character, as the logic below depends on this character not | |
| 396 // being included. | |
| 397 DCHECK(query[0] != '?' && query[0] != '#'); | |
| 398 | |
| 399 // We shouldn't try to find matches beyond the point where there aren't enough | |
| 400 // characters left in query to fully match the component. | |
| 401 const size_t last_search_start = query.length() - component.length(); | |
| 402 | |
| 403 // We need to search for matches in a loop, rather than stopping at the first | |
| 404 // match, because we may initially match a substring that isn't a full query | |
| 405 // string component. Consider, for instance, the query string 'ab=cd&b=c'. If | |
| 406 // we search for component 'b=c', the first substring match will be characters | |
| 407 // 1-3 (zero-based) in the query string. However, this isn't a full component | |
| 408 // (the full component is ab=cd) so the match will fail. Thus, we must | |
| 409 // continue our search to find the second substring match, which in the | |
| 410 // example is at characters 6-8 (the end of the query string) and is a | |
| 411 // successful component match. | |
| 412 for (size_t start_offset = 0; start_offset <= last_search_start; | |
| 413 start_offset += component.length()) { | |
| 414 start_offset = query.find(component, start_offset); | |
| 415 if (start_offset == std::string::npos) { | |
| 416 // We searched to end of string and did not find a match. | |
| 417 return false; | |
| 418 } | |
| 419 // Verify that the character prior to the component is valid (either we're | |
| 420 // at the beginning of the query string, or are preceded by an ampersand). | |
| 421 if (start_offset != 0 && query[start_offset - 1] != '&') { | |
| 422 continue; | |
| 423 } | |
| 424 if (!component_is_prefix) { | |
| 425 // Verify that the character after the component substring is valid | |
| 426 // (either we're at the end of the query string, or are followed by an | |
| 427 // ampersand). | |
| 428 const size_t after_offset = start_offset + component.length(); | |
| 429 if (after_offset < query.length() && query[after_offset] != '&') { | |
| 430 continue; | |
| 431 } | |
| 432 } | |
| 433 return true; | |
| 434 } | |
| 435 return false; | |
| 436 } | |
| 437 | |
| 438 FromGWSPageLoadMetricsLogger::FromGWSPageLoadMetricsLogger() {} | 319 FromGWSPageLoadMetricsLogger::FromGWSPageLoadMetricsLogger() {} |
| 439 | 320 |
| 440 void FromGWSPageLoadMetricsLogger::SetPreviouslyCommittedUrl(const GURL& url) { | 321 void FromGWSPageLoadMetricsLogger::SetPreviouslyCommittedUrl(const GURL& url) { |
| 441 previously_committed_url_is_search_results_ = IsGoogleSearchResultUrl(url); | 322 previously_committed_url_is_search_results_ = |
| 323 page_load_metrics::IsGoogleSearchResultUrl(url); |
| 442 previously_committed_url_is_search_redirector_ = | 324 previously_committed_url_is_search_redirector_ = |
| 443 IsGoogleSearchRedirectorUrl(url); | 325 page_load_metrics::IsGoogleSearchRedirectorUrl(url); |
| 444 } | 326 } |
| 445 | 327 |
| 446 void FromGWSPageLoadMetricsLogger::SetProvisionalUrl(const GURL& url) { | 328 void FromGWSPageLoadMetricsLogger::SetProvisionalUrl(const GURL& url) { |
| 447 provisional_url_has_search_hostname_ = IsGoogleSearchHostname(url); | 329 provisional_url_has_search_hostname_ = |
| 330 page_load_metrics::IsGoogleSearchHostname(url); |
| 448 } | 331 } |
| 449 | 332 |
| 450 FromGWSPageLoadMetricsObserver::FromGWSPageLoadMetricsObserver() {} | 333 FromGWSPageLoadMetricsObserver::FromGWSPageLoadMetricsObserver() {} |
| 451 | 334 |
| 452 page_load_metrics::PageLoadMetricsObserver::ObservePolicy | 335 page_load_metrics::PageLoadMetricsObserver::ObservePolicy |
| 453 FromGWSPageLoadMetricsObserver::OnStart( | 336 FromGWSPageLoadMetricsObserver::OnStart( |
| 454 content::NavigationHandle* navigation_handle, | 337 content::NavigationHandle* navigation_handle, |
| 455 const GURL& currently_committed_url, | 338 const GURL& currently_committed_url, |
| 456 bool started_in_foreground) { | 339 bool started_in_foreground) { |
| 457 logger_.SetPreviouslyCommittedUrl(currently_committed_url); | 340 logger_.SetPreviouslyCommittedUrl(currently_committed_url); |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 594 return; | 477 return; |
| 595 | 478 |
| 596 LogProvisionalAborts(abort_info); | 479 LogProvisionalAborts(abort_info); |
| 597 | 480 |
| 598 LogForegroundDurations(page_load_metrics::mojom::PageLoadTiming(), extra_info, | 481 LogForegroundDurations(page_load_metrics::mojom::PageLoadTiming(), extra_info, |
| 599 base::TimeTicks()); | 482 base::TimeTicks()); |
| 600 } | 483 } |
| 601 | 484 |
| 602 bool FromGWSPageLoadMetricsLogger::ShouldLogFailedProvisionalLoadMetrics() { | 485 bool FromGWSPageLoadMetricsLogger::ShouldLogFailedProvisionalLoadMetrics() { |
| 603 // See comment in ShouldLogPostCommitMetrics above the call to | 486 // See comment in ShouldLogPostCommitMetrics above the call to |
| 604 // IsGoogleSearchHostname for more info on this if test. | 487 // page_load_metrics::IsGoogleSearchHostname for more info on this if test. |
| 605 if (provisional_url_has_search_hostname_) | 488 if (provisional_url_has_search_hostname_) |
| 606 return false; | 489 return false; |
| 607 | 490 |
| 608 return previously_committed_url_is_search_results_ || | 491 return previously_committed_url_is_search_results_ || |
| 609 previously_committed_url_is_search_redirector_; | 492 previously_committed_url_is_search_redirector_; |
| 610 } | 493 } |
| 611 | 494 |
| 612 bool FromGWSPageLoadMetricsLogger::ShouldLogPostCommitMetrics(const GURL& url) { | 495 bool FromGWSPageLoadMetricsLogger::ShouldLogPostCommitMetrics(const GURL& url) { |
| 613 DCHECK(!url.is_empty()); | 496 DCHECK(!url.is_empty()); |
| 614 | 497 |
| 615 // If this page has a URL on a known google search hostname, then it may be a | 498 // If this page has a URL on a known google search hostname, then it may be a |
| 616 // page associated with search (either a search results page, or a search | 499 // page associated with search (either a search results page, or a search |
| 617 // redirector url), so we should not log stats. We could try to detect only | 500 // redirector url), so we should not log stats. We could try to detect only |
| 618 // the specific known search URLs here, and log navigations to other pages on | 501 // the specific known search URLs here, and log navigations to other pages on |
| 619 // the google search hostname (for example, a search for 'about google' | 502 // the google search hostname (for example, a search for 'about google' |
| 620 // includes a result for https://www.google.com/about/), however, we assume | 503 // includes a result for https://www.google.com/about/), however, we assume |
| 621 // these cases are relatively uncommon, and we run the risk of logging metrics | 504 // these cases are relatively uncommon, and we run the risk of logging metrics |
| 622 // for some search redirector URLs. Thus we choose the more conservative | 505 // for some search redirector URLs. Thus we choose the more conservative |
| 623 // approach of ignoring all urls on known search hostnames. | 506 // approach of ignoring all urls on known search hostnames. |
| 624 if (IsGoogleSearchHostname(url)) | 507 if (page_load_metrics::IsGoogleSearchHostname(url)) |
| 625 return false; | 508 return false; |
| 626 | 509 |
| 627 // We're only interested in tracking navigations (e.g. clicks) initiated via | 510 // We're only interested in tracking navigations (e.g. clicks) initiated via |
| 628 // links. Note that the redirector will mask these, so don't enforce this if | 511 // links. Note that the redirector will mask these, so don't enforce this if |
| 629 // the navigation came from a redirect url. TODO(csharrison): Use this signal | 512 // the navigation came from a redirect url. TODO(csharrison): Use this signal |
| 630 // for provisional loads when the content APIs allow for it. | 513 // for provisional loads when the content APIs allow for it. |
| 631 if (previously_committed_url_is_search_results_ && | 514 if (previously_committed_url_is_search_results_ && |
| 632 navigation_initiated_via_link_) { | 515 navigation_initiated_via_link_) { |
| 633 return true; | 516 return true; |
| 634 } | 517 } |
| (...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 748 first_user_interaction_after_paint_ = | 631 first_user_interaction_after_paint_ = |
| 749 base::TimeTicks::Now() - navigation_start_; | 632 base::TimeTicks::Now() - navigation_start_; |
| 750 } | 633 } |
| 751 } | 634 } |
| 752 | 635 |
| 753 void FromGWSPageLoadMetricsLogger::FlushMetricsOnAppEnterBackground( | 636 void FromGWSPageLoadMetricsLogger::FlushMetricsOnAppEnterBackground( |
| 754 const page_load_metrics::mojom::PageLoadTiming& timing, | 637 const page_load_metrics::mojom::PageLoadTiming& timing, |
| 755 const page_load_metrics::PageLoadExtraInfo& extra_info) { | 638 const page_load_metrics::PageLoadExtraInfo& extra_info) { |
| 756 LogForegroundDurations(timing, extra_info, base::TimeTicks::Now()); | 639 LogForegroundDurations(timing, extra_info, base::TimeTicks::Now()); |
| 757 } | 640 } |
| OLD | NEW |