source_url_recorder.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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
  3. // found in the LICENSE file.
  4. #include "components/ukm/content/source_url_recorder.h"
  5. #include <utility>
  6. #include "base/containers/flat_map.h"
  7. #include "base/memory/ptr_util.h"
  8. #include "base/metrics/field_trial_params.h"
  9. #include "content/public/browser/navigation_handle.h"
  10. #include "content/public/browser/web_contents.h"
  11. #include "content/public/browser/web_contents_observer.h"
  12. #include "content/public/browser/web_contents_user_data.h"
  13. #include "services/metrics/public/cpp/delegating_ukm_recorder.h"
  14. #include "services/metrics/public/cpp/ukm_builders.h"
  15. #include "services/metrics/public/cpp/ukm_recorder.h"
  16. #include "services/metrics/public/cpp/ukm_source_id.h"
  17. #include "url/gurl.h"
  18. namespace ukm {
  19. namespace internal {
  20. int64_t CreateUniqueTabId() {
  21. static int64_t unique_id_counter = 0;
  22. return ++unique_id_counter;
  23. }
  24. // SourceUrlRecorderWebContentsObserver is responsible for recording UKM source
  25. // URLs, for all (any only) main frame navigations in a given WebContents.
  26. // SourceUrlRecorderWebContentsObserver records both the final URL for a
  27. // navigation, and, if the navigation was redirected, the initial URL as well.
  28. class SourceUrlRecorderWebContentsObserver
  29. : public content::WebContentsObserver,
  30. public content::WebContentsUserData<
  31. SourceUrlRecorderWebContentsObserver> {
  32. public:
  33. SourceUrlRecorderWebContentsObserver(
  34. const SourceUrlRecorderWebContentsObserver&) = delete;
  35. SourceUrlRecorderWebContentsObserver& operator=(
  36. const SourceUrlRecorderWebContentsObserver&) = delete;
  37. // content::WebContentsObserver:
  38. void DidStartNavigation(
  39. content::NavigationHandle* navigation_handle) override;
  40. void DidFinishNavigation(
  41. content::NavigationHandle* navigation_handle) override;
  42. void DidOpenRequestedURL(content::WebContents* new_contents,
  43. content::RenderFrameHost* source_render_frame_host,
  44. const GURL& url,
  45. const content::Referrer& referrer,
  46. WindowOpenDisposition disposition,
  47. ui::PageTransition transition,
  48. bool started_from_context_menu,
  49. bool renderer_initiated) override;
  50. void WebContentsDestroyed() override;
  51. ukm::SourceId GetLastCommittedSourceId() const;
  52. ukm::SourceId GetLastCommittedFullNavigationOrSameDocumentSourceId() const;
  53. private:
  54. explicit SourceUrlRecorderWebContentsObserver(
  55. content::WebContents* web_contents);
  56. friend class content::WebContentsUserData<
  57. SourceUrlRecorderWebContentsObserver>;
  58. void HandleSameDocumentNavigation(
  59. content::NavigationHandle* navigation_handle);
  60. void HandleDifferentDocumentNavigation(
  61. content::NavigationHandle* navigation_handle,
  62. const GURL& initial_url);
  63. void MaybeRecordUrl(content::NavigationHandle* navigation_handle,
  64. const GURL& initial_url);
  65. // Whether URLs should be recorded in UKM Sources.
  66. bool ShouldRecordURLs() const;
  67. // Map from navigation ID to the initial URL for that navigation.
  68. base::flat_map<int64_t, GURL> pending_navigations_;
  69. // The source id of the last committed full navigation (where a full
  70. // navigation is a non-same-document navigation).
  71. SourceId last_committed_full_navigation_source_id_;
  72. // The source id of the last committed navigation, either full navigation or
  73. // same document.
  74. SourceId last_committed_full_navigation_or_same_document_source_id_;
  75. // The source id of the last committed source in the tab that opened this tab.
  76. // Will be set to kInvalidSourceId after the first navigation in this tab is
  77. // finished.
  78. SourceId opener_source_id_;
  79. const int64_t tab_id_;
  80. int num_same_document_sources_for_full_navigation_source_;
  81. WEB_CONTENTS_USER_DATA_KEY_DECL();
  82. };
  83. WEB_CONTENTS_USER_DATA_KEY_IMPL(SourceUrlRecorderWebContentsObserver);
  84. SourceUrlRecorderWebContentsObserver::SourceUrlRecorderWebContentsObserver(
  85. content::WebContents* web_contents)
  86. : content::WebContentsObserver(web_contents),
  87. content::WebContentsUserData<SourceUrlRecorderWebContentsObserver>(
  88. *web_contents),
  89. last_committed_full_navigation_source_id_(ukm::kInvalidSourceId),
  90. last_committed_full_navigation_or_same_document_source_id_(
  91. ukm::kInvalidSourceId),
  92. opener_source_id_(ukm::kInvalidSourceId),
  93. tab_id_(CreateUniqueTabId()),
  94. num_same_document_sources_for_full_navigation_source_(0) {}
  95. bool SourceUrlRecorderWebContentsObserver::ShouldRecordURLs() const {
  96. // TODO(crbug/1078349): ensure we only record URLs for tabs in a tab strip.
  97. // If there is an outer WebContents, then this WebContents is embedded into
  98. // another one (e.g it is a portal or a Chrome App <webview>).
  99. return web_contents()->GetOuterWebContents() == nullptr;
  100. }
  101. void SourceUrlRecorderWebContentsObserver::DidStartNavigation(
  102. content::NavigationHandle* navigation_handle) {
  103. // UKM only records URLs for main frame (web page) navigations, so ignore
  104. // non-main frame navs. Additionally, at least for the time being, we don't
  105. // track metrics for same-document navigations (e.g. changes in URL fragment,
  106. // or URL changes due to history.pushState) in UKM.
  107. if (!navigation_handle->IsInPrimaryMainFrame() ||
  108. navigation_handle->IsSameDocument()) {
  109. return;
  110. }
  111. // UKM doesn't want to record URLs for downloads. However, at the point a
  112. // navigation is started, we don't yet know if the navigation will result in a
  113. // download. Thus, we record the URL at the time a navigation was initiated,
  114. // and only record it later, once we verify that the navigation didn't result
  115. // in a download.
  116. pending_navigations_.insert(std::make_pair(
  117. navigation_handle->GetNavigationId(), navigation_handle->GetURL()));
  118. }
  119. void SourceUrlRecorderWebContentsObserver::DidFinishNavigation(
  120. content::NavigationHandle* navigation_handle) {
  121. auto it = pending_navigations_.find(navigation_handle->GetNavigationId());
  122. if (!navigation_handle->IsInPrimaryMainFrame()) {
  123. DCHECK(it == pending_navigations_.end());
  124. return;
  125. }
  126. if (navigation_handle->IsSameDocument()) {
  127. DCHECK(it == pending_navigations_.end());
  128. HandleSameDocumentNavigation(navigation_handle);
  129. return;
  130. }
  131. if (it != pending_navigations_.end()) {
  132. GURL initial_url = std::move(it->second);
  133. pending_navigations_.erase(it);
  134. HandleDifferentDocumentNavigation(navigation_handle, initial_url);
  135. }
  136. }
  137. void SourceUrlRecorderWebContentsObserver::HandleSameDocumentNavigation(
  138. content::NavigationHandle* navigation_handle) {
  139. if (!navigation_handle->HasCommitted())
  140. return;
  141. // Only record same-document sources if we were also recording the associated
  142. // full source.
  143. if (last_committed_full_navigation_source_id_ == ukm::kInvalidSourceId) {
  144. return;
  145. }
  146. // Since the navigation has committed, inform the UKM recorder that the
  147. // previous same-document source (if applicable) is no longer needed to be
  148. // kept alive in memory since we had navigated away. If the previous
  149. // navigation was a full navigation, we do not mark its source id since events
  150. // could be continued to be reported for it until the next full navigation
  151. // source is committed.
  152. ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
  153. if (ukm_recorder &&
  154. GetLastCommittedSourceId() !=
  155. GetLastCommittedFullNavigationOrSameDocumentSourceId()) {
  156. ukm_recorder->MarkSourceForDeletion(
  157. GetLastCommittedFullNavigationOrSameDocumentSourceId());
  158. }
  159. MaybeRecordUrl(navigation_handle, GURL::EmptyGURL());
  160. last_committed_full_navigation_or_same_document_source_id_ =
  161. ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
  162. ukm::SourceIdType::NAVIGATION_ID);
  163. ++num_same_document_sources_for_full_navigation_source_;
  164. }
  165. void SourceUrlRecorderWebContentsObserver::HandleDifferentDocumentNavigation(
  166. content::NavigationHandle* navigation_handle,
  167. const GURL& initial_url) {
  168. // UKM doesn't want to record URLs for navigations that result in downloads.
  169. if (navigation_handle->IsDownload())
  170. return;
  171. // If a new full navigation has been committed, there will be no more events
  172. // associated with previous navigation sources, so we mark them as obsolete.
  173. ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
  174. if (navigation_handle->HasCommitted() && ukm_recorder) {
  175. // Source id of the previous full navigation.
  176. ukm_recorder->MarkSourceForDeletion(GetLastCommittedSourceId());
  177. // Source id of the previous navigation. If the previous navigation is a
  178. // full navigation, marking it again has no additional effect.
  179. ukm_recorder->MarkSourceForDeletion(
  180. GetLastCommittedFullNavigationOrSameDocumentSourceId());
  181. }
  182. MaybeRecordUrl(navigation_handle, initial_url);
  183. if (navigation_handle->HasCommitted()) {
  184. last_committed_full_navigation_source_id_ = ukm::ConvertToSourceId(
  185. navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
  186. last_committed_full_navigation_or_same_document_source_id_ =
  187. last_committed_full_navigation_source_id_;
  188. num_same_document_sources_for_full_navigation_source_ = 0;
  189. }
  190. // Reset the opener source id. Only the first source in a tab should have an
  191. // opener.
  192. opener_source_id_ = kInvalidSourceId;
  193. }
  194. void SourceUrlRecorderWebContentsObserver::DidOpenRequestedURL(
  195. content::WebContents* new_contents,
  196. content::RenderFrameHost* source_render_frame_host,
  197. const GURL& url,
  198. const content::Referrer& referrer,
  199. WindowOpenDisposition disposition,
  200. ui::PageTransition transition,
  201. bool started_from_context_menu,
  202. bool renderer_initiated) {
  203. auto* new_recorder =
  204. SourceUrlRecorderWebContentsObserver::FromWebContents(new_contents);
  205. if (!new_recorder)
  206. return;
  207. new_recorder->opener_source_id_ = GetLastCommittedSourceId();
  208. }
  209. void SourceUrlRecorderWebContentsObserver::WebContentsDestroyed() {
  210. // Inform the UKM recorder that the previous source is no longer needed to
  211. // be kept alive in memory since the tab has been closed or discarded. In case
  212. // of same-document navigation, a new source id would have been created
  213. // similarly to full-navigation, thus we are marking the last committed source
  214. // id regardless of which case it came from.
  215. ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
  216. if (ukm_recorder) {
  217. ukm_recorder->MarkSourceForDeletion(
  218. GetLastCommittedFullNavigationOrSameDocumentSourceId());
  219. }
  220. }
  221. ukm::SourceId SourceUrlRecorderWebContentsObserver::GetLastCommittedSourceId()
  222. const {
  223. return last_committed_full_navigation_source_id_;
  224. }
  225. ukm::SourceId SourceUrlRecorderWebContentsObserver::
  226. GetLastCommittedFullNavigationOrSameDocumentSourceId() const {
  227. return last_committed_full_navigation_or_same_document_source_id_;
  228. }
  229. void SourceUrlRecorderWebContentsObserver::MaybeRecordUrl(
  230. content::NavigationHandle* navigation_handle,
  231. const GURL& initial_url) {
  232. DCHECK(navigation_handle->IsInPrimaryMainFrame());
  233. // TODO(crbug/1078355): If ShouldRecordURLs is false, we should still create a
  234. // UKM source, but not add any URLs to it.
  235. if (!ShouldRecordURLs())
  236. return;
  237. ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
  238. if (!ukm_recorder)
  239. return;
  240. UkmSource::NavigationData navigation_data;
  241. const GURL& final_url = navigation_handle->GetURL();
  242. // TODO(crbug.com/869123): This check isn't quite correct, as self redirecting
  243. // is possible. This may also be changed to include the entire redirect chain.
  244. // Additionally, since same-document navigations don't have initial URLs,
  245. // ignore empty initial URLs.
  246. if (!initial_url.is_empty() && final_url != initial_url)
  247. navigation_data.urls = {initial_url};
  248. navigation_data.urls.push_back(final_url);
  249. navigation_data.is_same_document_navigation =
  250. navigation_handle->IsSameDocument();
  251. navigation_data.same_origin_status =
  252. UkmSource::NavigationData::SameOriginStatus::UNSET;
  253. // Only set the same origin flag for committed non-error,
  254. // non-same-document navigations.
  255. if (navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage() &&
  256. !navigation_handle->IsSameDocument()) {
  257. navigation_data.same_origin_status =
  258. navigation_handle->IsSameOrigin()
  259. ? UkmSource::NavigationData::SameOriginStatus::SAME_ORIGIN
  260. : UkmSource::NavigationData::SameOriginStatus::CROSS_ORIGIN;
  261. }
  262. navigation_data.is_renderer_initiated =
  263. navigation_handle->IsRendererInitiated();
  264. navigation_data.is_error_page = navigation_handle->IsErrorPage();
  265. navigation_data.previous_source_id =
  266. last_committed_full_navigation_source_id_;
  267. navigation_data.navigation_time = navigation_handle->NavigationStart();
  268. // If the last_committed_full_navigation_or_same_document_source_id_ isn't
  269. // equal to the last_committed_full_navigation_source_id_, it indicates the
  270. // previous source was a same document navigation.
  271. const bool previous_source_was_same_document_navigation =
  272. last_committed_full_navigation_or_same_document_source_id_ !=
  273. last_committed_full_navigation_source_id_;
  274. if (previous_source_was_same_document_navigation) {
  275. navigation_data.previous_same_document_source_id =
  276. last_committed_full_navigation_or_same_document_source_id_;
  277. }
  278. navigation_data.opener_source_id = opener_source_id_;
  279. navigation_data.tab_id = tab_id_;
  280. const ukm::SourceId source_id = ukm::ConvertToSourceId(
  281. navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
  282. ukm_recorder->RecordNavigation(source_id, navigation_data);
  283. }
  284. } // namespace internal
  285. void InitializeSourceUrlRecorderForWebContents(
  286. content::WebContents* web_contents) {
  287. internal::SourceUrlRecorderWebContentsObserver::CreateForWebContents(
  288. web_contents);
  289. }
  290. } // namespace ukm