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