1 // Copyright (c) 2012 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 "chrome/browser/history/history_tab_helper.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/stl_util.h"
11 #include "build/build_config.h"
12 #include "chrome/browser/history/history_service_factory.h"
13 #include "chrome/browser/prefetch/no_state_prefetch/prerender_manager_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "components/history/content/browser/history_context_helper.h"
16 #include "components/history/core/browser/history_backend.h"
17 #include "components/history/core/browser/history_constants.h"
18 #include "components/history/core/browser/history_service.h"
19 #include "components/no_state_prefetch/browser/prerender_manager.h"
20 #include "content/public/browser/navigation_entry.h"
21 #include "content/public/browser/navigation_handle.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/browser/web_contents_delegate.h"
25 #include "third_party/blink/public/mojom/loader/referrer.mojom.h"
26 #include "ui/base/page_transition_types.h"
27
28 #if defined(OS_ANDROID)
29 #include "chrome/browser/android/background_tab_manager.h"
30 #include "components/feed/feed_feature_list.h"
31 #else
32 #include "chrome/browser/ui/browser.h"
33 #include "chrome/browser/ui/browser_finder.h"
34 #endif
35
36 #if defined(OS_ANDROID)
37 using chrome::android::BackgroundTabManager;
38 #endif
39
40 using content::NavigationEntry;
41 using content::WebContents;
42
HistoryTabHelper(WebContents * web_contents)43 HistoryTabHelper::HistoryTabHelper(WebContents* web_contents)
44 : content::WebContentsObserver(web_contents) {}
45
~HistoryTabHelper()46 HistoryTabHelper::~HistoryTabHelper() {}
47
UpdateHistoryForNavigation(const history::HistoryAddPageArgs & add_page_args)48 void HistoryTabHelper::UpdateHistoryForNavigation(
49 const history::HistoryAddPageArgs& add_page_args) {
50 history::HistoryService* hs = GetHistoryService();
51 if (hs)
52 hs->AddPage(add_page_args);
53 }
54
CreateHistoryAddPageArgs(const GURL & virtual_url,base::Time timestamp,int nav_entry_id,content::NavigationHandle * navigation_handle)55 history::HistoryAddPageArgs HistoryTabHelper::CreateHistoryAddPageArgs(
56 const GURL& virtual_url,
57 base::Time timestamp,
58 int nav_entry_id,
59 content::NavigationHandle* navigation_handle) {
60 ui::PageTransition page_transition = navigation_handle->GetPageTransition();
61 #if defined(OS_ANDROID)
62 // Clicks on content suggestions on the NTP should not contribute to the
63 // Most Visited tiles in the NTP.
64 const GURL& referrer_url = navigation_handle->GetReferrer().url;
65 const bool content_suggestions_navigation =
66 referrer_url == feed::GetFeedReferrerUrl() &&
67 ui::PageTransitionCoreTypeIs(page_transition,
68 ui::PAGE_TRANSITION_AUTO_BOOKMARK);
69 #else
70 const bool content_suggestions_navigation = false;
71 #endif
72
73 const bool status_code_is_error =
74 navigation_handle->GetResponseHeaders() &&
75 (navigation_handle->GetResponseHeaders()->response_code() >= 400) &&
76 (navigation_handle->GetResponseHeaders()->response_code() < 600);
77 // Top-level frame navigations are visible, unless hiding all visits;
78 // everything else is hidden. Also hide top-level navigations that result in
79 // an error in order to prevent the omnibox from suggesting URLs that have
80 // never been navigated to successfully. (If a top-level navigation to the
81 // URL succeeds at some point, the URL will be unhidden and thus eligible to
82 // be suggested by the omnibox.)
83 // Don't attempt hide navigations that increment the typed count. Doing that
84 // would lead to a state where the omnibox would suggest urls that don't
85 // show up in history.
86 const bool hide_normally_visible_navigation =
87 hide_all_navigations_ && ui::PageTransitionIsMainFrame(page_transition) &&
88 !history::HistoryBackend::IsTypedIncrement(page_transition);
89 const bool hidden = hide_normally_visible_navigation ||
90 !ui::PageTransitionIsMainFrame(page_transition) ||
91 status_code_is_error;
92 if (hide_normally_visible_navigation) {
93 // Add PAGE_TRANSITION_FROM_API_3 so that VisitsDatabase won't return this
94 // visit in queries for visible visits.
95 page_transition = ui::PageTransitionFromInt(ui::PAGE_TRANSITION_FROM_API_3 |
96 page_transition);
97 }
98 history::HistoryAddPageArgs add_page_args(
99 navigation_handle->GetURL(), timestamp,
100 history::ContextIDForWebContents(web_contents()), nav_entry_id,
101 navigation_handle->GetReferrer().url,
102 navigation_handle->GetRedirectChain(), page_transition, hidden,
103 history::SOURCE_BROWSED, navigation_handle->DidReplaceEntry(),
104 !content_suggestions_navigation,
105 navigation_handle->GetSocketAddress().address().IsPubliclyRoutable(),
106 navigation_handle->IsSameDocument()
107 ? base::Optional<base::string16>(
108 navigation_handle->GetWebContents()->GetTitle())
109 : base::nullopt);
110
111 if (ui::PageTransitionIsMainFrame(page_transition) &&
112 virtual_url != navigation_handle->GetURL()) {
113 // Hack on the "virtual" URL so that it will appear in history. For some
114 // types of URLs, we will display a magic URL that is different from where
115 // the page is actually navigated. We want the user to see in history what
116 // they saw in the URL bar, so we add the virtual URL as a redirect. This
117 // only applies to the main frame, as the virtual URL doesn't apply to
118 // sub-frames.
119 add_page_args.url = virtual_url;
120 if (!add_page_args.redirects.empty())
121 add_page_args.redirects.back() = virtual_url;
122 }
123 return add_page_args;
124 }
125
DidFinishNavigation(content::NavigationHandle * navigation_handle)126 void HistoryTabHelper::DidFinishNavigation(
127 content::NavigationHandle* navigation_handle) {
128 if (!navigation_handle->HasCommitted())
129 return;
130
131 if (navigation_handle->IsInMainFrame()) {
132 is_loading_ = true;
133 num_title_changes_ = 0;
134 } else if (!navigation_handle->HasSubframeNavigationEntryCommitted()) {
135 // Filter out unwanted URLs. We don't add auto-subframe URLs that don't
136 // change which NavigationEntry is current. They are a large part of history
137 // (think iframes for ads) and we never display them in history UI. We will
138 // still add manual subframes, which are ones the user has clicked on to
139 // get.
140 return;
141 }
142
143 // Update history. Note that this needs to happen after the entry is complete,
144 // which WillNavigate[Main,Sub]Frame will do before this function is called.
145 if (!navigation_handle->ShouldUpdateHistory())
146 return;
147
148 // Navigations in portals don't appear in history until the portal is
149 // activated.
150 if (navigation_handle->GetWebContents()->IsPortal())
151 return;
152
153 // Prerenders should not update history. Prerenders will have their own
154 // WebContents with all observers (including |this|), and go through the
155 // normal flow of a navigation, including commit.
156 prerender::PrerenderManager* prerender_manager =
157 prerender::PrerenderManagerFactory::GetForBrowserContext(
158 web_contents()->GetBrowserContext());
159 if (prerender_manager &&
160 prerender_manager->IsWebContentsPrerendering(web_contents())) {
161 return;
162 }
163
164 // Most of the time, the displayURL matches the loaded URL, but for about:
165 // URLs, we use a data: URL as the real value. We actually want to save the
166 // about: URL to the history db and keep the data: URL hidden. This is what
167 // the WebContents' URL getter does.
168 NavigationEntry* last_committed =
169 web_contents()->GetController().GetLastCommittedEntry();
170 const history::HistoryAddPageArgs& add_page_args = CreateHistoryAddPageArgs(
171 web_contents()->GetLastCommittedURL(), last_committed->GetTimestamp(),
172 last_committed->GetUniqueID(), navigation_handle);
173
174 #if defined(OS_ANDROID)
175 auto* background_tab_manager = BackgroundTabManager::GetInstance();
176 if (background_tab_manager->IsBackgroundTab(web_contents())) {
177 // No history insertion is done for now since this is a tab that speculates
178 // future navigations. Just caching and returning for now.
179 background_tab_manager->CacheHistory(add_page_args);
180 return;
181 }
182 #else
183 // Don't update history if this web contents isn't associated with a tab.
184 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
185 if (!browser)
186 return;
187 #endif
188
189 UpdateHistoryForNavigation(add_page_args);
190 }
191
192 // We update history upon the associated WebContents becoming the top level
193 // contents of a tab from portal activation.
194 // TODO(mcnee): Investigate whether the early return cases in
195 // DidFinishNavigation apply to portal activation. See https://crbug.com/1072762
DidActivatePortal(content::WebContents * predecessor_contents,base::TimeTicks activation_time)196 void HistoryTabHelper::DidActivatePortal(
197 content::WebContents* predecessor_contents,
198 base::TimeTicks activation_time) {
199 history::HistoryService* hs = GetHistoryService();
200 if (!hs)
201 return;
202
203 content::NavigationEntry* last_committed_entry =
204 web_contents()->GetController().GetLastCommittedEntry();
205
206 // TODO(1058504): Update this when portal activations can be done with
207 // replacement.
208 const bool did_replace_entry = false;
209
210 const history::HistoryAddPageArgs add_page_args(
211 last_committed_entry->GetVirtualURL(),
212 last_committed_entry->GetTimestamp(),
213 history::ContextIDForWebContents(web_contents()),
214 last_committed_entry->GetUniqueID(),
215 last_committed_entry->GetReferrer().url,
216 /* redirects */ {}, ui::PAGE_TRANSITION_LINK,
217 /* hidden */ false, history::SOURCE_BROWSED, did_replace_entry,
218 /* consider_for_ntp_most_visited */ true,
219 /* publicly_routable */ false, last_committed_entry->GetTitle());
220 hs->AddPage(add_page_args);
221 }
222
DidFinishLoad(content::RenderFrameHost * render_frame_host,const GURL & validated_url)223 void HistoryTabHelper::DidFinishLoad(
224 content::RenderFrameHost* render_frame_host,
225 const GURL& validated_url) {
226 if (render_frame_host->GetParent())
227 return;
228
229 is_loading_ = false;
230 last_load_completion_ = base::TimeTicks::Now();
231 }
232
TitleWasSet(NavigationEntry * entry)233 void HistoryTabHelper::TitleWasSet(NavigationEntry* entry) {
234 if (!entry)
235 return;
236
237 // Protect against pages changing their title too often.
238 if (num_title_changes_ >= history::kMaxTitleChanges)
239 return;
240
241 // Only store page titles into history if they were set while the page was
242 // loading or during a brief span after load is complete. This fixes the case
243 // where a page uses a title change to alert a user of a situation but that
244 // title change ends up saved in history.
245 if (is_loading_ || (base::TimeTicks::Now() - last_load_completion_ <
246 history::GetTitleSettingWindow())) {
247 history::HistoryService* hs = GetHistoryService();
248 if (hs) {
249 hs->SetPageTitle(entry->GetVirtualURL(), entry->GetTitleForDisplay());
250 ++num_title_changes_;
251 }
252 }
253 }
254
GetHistoryService()255 history::HistoryService* HistoryTabHelper::GetHistoryService() {
256 Profile* profile =
257 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
258 if (profile->IsOffTheRecord())
259 return NULL;
260
261 return HistoryServiceFactory::GetForProfile(
262 profile, ServiceAccessType::IMPLICIT_ACCESS);
263 }
264
WebContentsDestroyed()265 void HistoryTabHelper::WebContentsDestroyed() {
266 // We update the history for this URL.
267 WebContents* tab = web_contents();
268 Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
269 if (profile->IsOffTheRecord())
270 return;
271
272 history::HistoryService* hs = HistoryServiceFactory::GetForProfile(
273 profile, ServiceAccessType::IMPLICIT_ACCESS);
274 if (hs) {
275 NavigationEntry* entry = tab->GetController().GetLastCommittedEntry();
276 history::ContextID context_id = history::ContextIDForWebContents(tab);
277 if (entry) {
278 hs->UpdateWithPageEndTime(context_id, entry->GetUniqueID(),
279 tab->GetLastCommittedURL(), base::Time::Now());
280 }
281 hs->ClearCachedDataForContextID(context_id);
282 }
283 }
284
285 WEB_CONTENTS_USER_DATA_KEY_IMPL(HistoryTabHelper)
286