ukm_url_recorder.mm 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // Copyright 2018 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. #include "components/ukm/ios/ukm_url_recorder.h"
  5. #include <utility>
  6. #include "base/containers/flat_map.h"
  7. #import "ios/web/public/navigation/navigation_context.h"
  8. #import "ios/web/public/web_state.h"
  9. #include "ios/web/public/web_state_observer.h"
  10. #import "ios/web/public/web_state_user_data.h"
  11. #include "services/metrics/public/cpp/delegating_ukm_recorder.h"
  12. #include "services/metrics/public/cpp/ukm_source_id.h"
  13. #include "url/gurl.h"
  14. #if !defined(__has_feature) || !__has_feature(objc_arc)
  15. #error "This file requires ARC support."
  16. #endif
  17. namespace ukm {
  18. namespace internal {
  19. // SourceUrlRecorderWebStateObserver is responsible for recording UKM source
  20. // URLs for all main frame navigations in a given WebState.
  21. // SourceUrlRecorderWebStateObserver records both the final URL for a
  22. // navigation and, if the navigation was redirected, the initial URL as well.
  23. class SourceUrlRecorderWebStateObserver
  24. : public web::WebStateObserver,
  25. public web::WebStateUserData<SourceUrlRecorderWebStateObserver> {
  26. public:
  27. SourceUrlRecorderWebStateObserver(const SourceUrlRecorderWebStateObserver&) =
  28. delete;
  29. SourceUrlRecorderWebStateObserver& operator=(
  30. const SourceUrlRecorderWebStateObserver&) = delete;
  31. ~SourceUrlRecorderWebStateObserver() override;
  32. // web::WebStateObserver
  33. void DidStartNavigation(web::WebState* web_state,
  34. web::NavigationContext* navigation_context) override;
  35. void DidFinishNavigation(web::WebState* web_state,
  36. web::NavigationContext* navigation_context) override;
  37. void WebStateDestroyed(web::WebState* web_state) override;
  38. SourceId GetLastCommittedSourceId() const;
  39. private:
  40. explicit SourceUrlRecorderWebStateObserver(web::WebState* web_state);
  41. friend class web::WebStateUserData<SourceUrlRecorderWebStateObserver>;
  42. void MaybeRecordUrl(web::NavigationContext* navigation_context,
  43. const GURL& initial_url);
  44. // Map from navigation ID to the initial URL for that navigation.
  45. base::flat_map<int64_t, GURL> pending_navigations_;
  46. SourceId last_committed_source_id_;
  47. WEB_STATE_USER_DATA_KEY_DECL();
  48. };
  49. WEB_STATE_USER_DATA_KEY_IMPL(SourceUrlRecorderWebStateObserver)
  50. SourceUrlRecorderWebStateObserver::SourceUrlRecorderWebStateObserver(
  51. web::WebState* web_state)
  52. : last_committed_source_id_(kInvalidSourceId) {
  53. web_state->AddObserver(this);
  54. }
  55. SourceUrlRecorderWebStateObserver::~SourceUrlRecorderWebStateObserver() {}
  56. void SourceUrlRecorderWebStateObserver::WebStateDestroyed(
  57. web::WebState* web_state) {
  58. web_state->RemoveObserver(this);
  59. }
  60. void SourceUrlRecorderWebStateObserver::DidStartNavigation(
  61. web::WebState* web_state,
  62. web::NavigationContext* navigation_context) {
  63. // NavigationContexts only exist for main frame navigations, so this will
  64. // never be called for non-main frame navigations and we don't need to filter
  65. // non-main frame navigations out here.
  66. // Additionally, at least for the time being, we don't track metrics for
  67. // same-document navigations (e.g. changes in URL fragment or URL changes
  68. // due to history.pushState) in UKM.
  69. if (navigation_context->IsSameDocument())
  70. return;
  71. // UKM doesn't want to record URLs for downloads. However, at the point a
  72. // navigation is started, we don't yet know if the navigation will result in a
  73. // download. Thus, we store the URL at the time a navigation was initiated
  74. // and only record it later, once we verify that the navigation didn't result
  75. // in a download.
  76. pending_navigations_.insert(std::make_pair(
  77. navigation_context->GetNavigationId(), navigation_context->GetUrl()));
  78. }
  79. void SourceUrlRecorderWebStateObserver::DidFinishNavigation(
  80. web::WebState* web_state,
  81. web::NavigationContext* navigation_context) {
  82. // NavigationContexts only exist for main frame navigations, so this will
  83. // never be called for non-main frame navigations and we don't need to filter
  84. // non-main frame navigations out here.
  85. auto it = pending_navigations_.find(navigation_context->GetNavigationId());
  86. if (it == pending_navigations_.end())
  87. return;
  88. DCHECK(!navigation_context->IsSameDocument());
  89. if (navigation_context->HasCommitted()) {
  90. last_committed_source_id_ = ConvertToSourceId(
  91. navigation_context->GetNavigationId(), SourceIdType::NAVIGATION_ID);
  92. }
  93. GURL initial_url = std::move(it->second);
  94. pending_navigations_.erase(it);
  95. // UKM doesn't want to record URLs for navigations that result in downloads.
  96. if (navigation_context->IsDownload())
  97. return;
  98. MaybeRecordUrl(navigation_context, initial_url);
  99. }
  100. SourceId SourceUrlRecorderWebStateObserver::GetLastCommittedSourceId() const {
  101. return last_committed_source_id_;
  102. }
  103. void SourceUrlRecorderWebStateObserver::MaybeRecordUrl(
  104. web::NavigationContext* navigation_context,
  105. const GURL& initial_url) {
  106. DCHECK(!navigation_context->IsSameDocument());
  107. DelegatingUkmRecorder* ukm_recorder = DelegatingUkmRecorder::Get();
  108. if (!ukm_recorder)
  109. return;
  110. const GURL& final_url = navigation_context->GetUrl();
  111. UkmSource::NavigationData navigation_data;
  112. // TODO(crbug.com/869123): This check isn't quite correct, as self redirecting
  113. // is possible. This may also be changed to include the entire redirect chain.
  114. if (final_url != initial_url)
  115. navigation_data.urls = {initial_url};
  116. navigation_data.urls.push_back(final_url);
  117. // TODO(crbug.com/873316): Fill out the other fields in NavigationData.
  118. const ukm::SourceId source_id = ukm::ConvertToSourceId(
  119. navigation_context->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
  120. ukm_recorder->RecordNavigation(source_id, navigation_data);
  121. }
  122. } // namespace internal
  123. void InitializeSourceUrlRecorderForWebState(web::WebState* web_state) {
  124. internal::SourceUrlRecorderWebStateObserver::CreateForWebState(web_state);
  125. }
  126. SourceId GetSourceIdForWebStateDocument(web::WebState* web_state) {
  127. internal::SourceUrlRecorderWebStateObserver* obs =
  128. internal::SourceUrlRecorderWebStateObserver::FromWebState(web_state);
  129. return obs ? obs->GetLastCommittedSourceId() : kInvalidSourceId;
  130. }
  131. } // namespace ukm