 Chromium Code Reviews
 Chromium Code Reviews Issue 2302913003:
  Add SafeBrowsingNavigationObserver to listen to navigation events  (Closed)
    
  
    Issue 2302913003:
  Add SafeBrowsingNavigationObserver to listen to navigation events  (Closed) 
  | Index: chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc | 
| diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..524e356eee737e68a40ccb8faa2de4452f42dc99 | 
| --- /dev/null | 
| +++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc | 
| @@ -0,0 +1,328 @@ | 
| +// Copyright 2016 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h" | 
| + | 
| +#include "base/memory/ptr_util.h" | 
| +#include "base/time/time.h" | 
| +#include "chrome/browser/chrome_notification_types.h" | 
| +#include "chrome/browser/sessions/session_tab_helper.h" | 
| +#include "chrome/browser/tab_contents/retargeting_details.h" | 
| +#include "content/public/browser/navigation_details.h" | 
| +#include "content/public/browser/navigation_handle.h" | 
| +#include "content/public/browser/notification_service.h" | 
| +#include "content/public/browser/notification_types.h" | 
| +#include "content/public/browser/render_frame_host.h" | 
| +#include "content/public/browser/render_process_host.h" | 
| +#include "content/public/browser/resource_request_details.h" | 
| +#include "content/public/browser/web_contents.h" | 
| +#include "content/public/common/resource_type.h" | 
| + | 
| +using content::WebContents; | 
| +namespace safe_browsing { | 
| + | 
| +static const double kUserGestureTTLInSecond = 0.5; | 
| + | 
| +bool IsExpired(const base::Time& timestamp) { | 
| 
Charlie Reis
2016/09/23 23:14:13
nit: IsUserGestureExpired?
 
Jialiu Lin
2016/09/27 17:50:59
Done.
 | 
| + double now = base::Time::Now().ToDoubleT(); | 
| + double timestamp_in_double = timestamp.ToDoubleT(); | 
| + return now > timestamp_in_double | 
| + ? now - timestamp_in_double > kUserGestureTTLInSecond | 
| + : true; | 
| +} | 
| + | 
| +// SafeBrowsingNavigationObserver::NavigationEvent----------------------------- | 
| +NavigationEvent::NavigationEvent() | 
| + : source_url(), | 
| + source_tab_id(-1), | 
| + target_url(), | 
| + target_tab_id(-1), | 
| + frame_id(-1), | 
| + main_frame_url(), | 
| + timestamp(base::Time::Now()), | 
| + is_user_initiated(false), | 
| + has_committed(false), | 
| + is_server_redirect(false), | 
| + server_redirect_url() {} | 
| + | 
| +NavigationEvent::NavigationEvent(NavigationEvent&& nav_event) | 
| + : source_url(std::move(nav_event.source_url)), | 
| + source_tab_id(std::move(nav_event.source_tab_id)), | 
| + target_url(std::move(nav_event.target_url)), | 
| + target_tab_id(std::move(nav_event.target_tab_id)), | 
| + frame_id(nav_event.frame_id), | 
| + main_frame_url(std::move(nav_event.main_frame_url)), | 
| + timestamp(nav_event.timestamp), | 
| + is_user_initiated(nav_event.is_user_initiated), | 
| + has_committed(nav_event.has_committed), | 
| + is_server_redirect(nav_event.is_server_redirect), | 
| + server_redirect_url(std::move(nav_event.server_redirect_url)) {} | 
| + | 
| +NavigationEvent& NavigationEvent::operator=(NavigationEvent&& nav_event) { | 
| + source_url = std::move(nav_event.source_url); | 
| + source_tab_id = std::move(nav_event.source_tab_id); | 
| + target_url = std::move(nav_event.target_url); | 
| + target_tab_id = std::move(nav_event.target_tab_id); | 
| + frame_id = nav_event.frame_id; | 
| + main_frame_url = std::move(nav_event.main_frame_url); | 
| + timestamp = nav_event.timestamp; | 
| + is_user_initiated = nav_event.is_user_initiated; | 
| + has_committed = nav_event.has_committed; | 
| + is_server_redirect = nav_event.is_server_redirect; | 
| + server_redirect_url = std::move(nav_event.server_redirect_url); | 
| + return *this; | 
| +} | 
| + | 
| +NavigationEvent::~NavigationEvent() {} | 
| + | 
| +// -------------------SBNavigationObserverManager Functions---------------- | 
| + | 
| +SBNavigationObserverManager::SBNavigationObserverManager() { | 
| + registrar_.Add(this, chrome::NOTIFICATION_RETARGETING, | 
| + content::NotificationService::AllSources()); | 
| +} | 
| + | 
| +void SBNavigationObserverManager::RecordNavigationEvent( | 
| + const GURL& nav_event_key, | 
| + NavigationEvent* nav_event) { | 
| + auto insertion_result = navigation_map_.insert( | 
| + std::make_pair(nav_event_key, std::vector<NavigationEvent>())); | 
| 
Charlie Reis
2016/09/23 23:14:14
Maybe I'm misreading this, but isn't this only nee
 
Jialiu Lin
2016/09/27 17:50:59
http://en.cppreference.com/w/cpp/container/unorder
 
Charlie Reis
2016/09/28 21:08:13
Acknowledged.
 | 
| + | 
| + insertion_result.first->second.push_back(std::move(*nav_event)); | 
| +} | 
| + | 
| +void SBNavigationObserverManager::RecordUserGestureForWebContents( | 
| + content::WebContents* web_contents, | 
| + const base::Time& timestamp) { | 
| + auto insertion_result = | 
| + user_gesture_map_.insert(std::make_pair(web_contents, timestamp)); | 
| + if (!insertion_result.second) | 
| + insertion_result.first->second = timestamp; | 
| +} | 
| + | 
| +void SBNavigationObserverManager::OnUserGestureConsumed( | 
| + content::WebContents* web_contents, | 
| + const base::Time& timestamp) { | 
| + auto it = user_gesture_map_.find(web_contents); | 
| + if (it == user_gesture_map_.end()) | 
| + return; | 
| + if (timestamp >= it->second) | 
| + user_gesture_map_.erase(web_contents); | 
| +} | 
| + | 
| +void SBNavigationObserverManager::OnWebContentDestroyed( | 
| + content::WebContents* web_contents) { | 
| + user_gesture_map_.erase(web_contents); | 
| + // TODO (jialiul): Will add other clean up tasks shortly. | 
| +} | 
| + | 
| +SBNavigationObserverManager::~SBNavigationObserverManager(){} | 
| + | 
| +void SBNavigationObserverManager::Observe( | 
| + int type, | 
| + const content::NotificationSource& source, | 
| + const content::NotificationDetails& details) { | 
| + if (type == chrome::NOTIFICATION_RETARGETING) | 
| + RecordRetargeting(details); | 
| +} | 
| + | 
| +void SBNavigationObserverManager::RecordRetargeting( | 
| + const content::NotificationDetails& details) { | 
| + const RetargetingDetails* retargeting_detail = | 
| + content::Details<const RetargetingDetails>(details).ptr(); | 
| + DCHECK(retargeting_detail); | 
| + content::WebContents* source_contents = | 
| + retargeting_detail->source_web_contents; | 
| + content::WebContents* target_contents = | 
| + retargeting_detail->target_web_contents; | 
| + DCHECK(source_contents); | 
| + DCHECK(target_contents); | 
| + | 
| + content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( | 
| + source_contents->GetRenderProcessHost()->GetID(), | 
| 
Charlie Reis
2016/09/23 23:14:13
This would break with OOPIFs, since WebContents::G
 
Jialiu Lin
2016/09/27 17:50:59
Thanks, I will add a comments here.
 | 
| + retargeting_detail->source_render_frame_id); | 
| + GURL::Replacements replacements; | 
| + replacements.ClearRef(); | 
| 
Charlie Reis
2016/09/23 23:14:13
Can you help me understand why we're clearing the
 
Jialiu Lin
2016/09/27 17:50:59
yes, this is to make "http://foo.com#bar" treated
 
Charlie Reis
2016/09/28 21:08:13
Sure.  For both #bar and pushState/replaceState, w
 
Jialiu Lin
2016/09/28 21:39:14
I see.  How about I just remove empty ref (a.k.a "
 | 
| + const GURL& target_url = | 
| + retargeting_detail->target_url.ReplaceComponents(replacements); | 
| + | 
| + navigation_map_.insert( | 
| + std::make_pair(target_url, std::vector<NavigationEvent>())); | 
| + | 
| + NavigationEvent nav_event; | 
| + nav_event.source_url = | 
| + rfh ? rfh->GetLastCommittedURL().ReplaceComponents(replacements) : GURL(); | 
| + nav_event.source_tab_id = SessionTabHelper::IdForTab(source_contents); | 
| + nav_event.target_url = target_url; | 
| + nav_event.target_tab_id = SessionTabHelper::IdForTab(target_contents); | 
| + nav_event.frame_id = rfh ? rfh->GetFrameTreeNodeId() : -1; | 
| + nav_event.main_frame_url = | 
| + source_contents->GetLastCommittedURL().ReplaceComponents(replacements); | 
| + nav_event.timestamp = base::Time::Now(); | 
| + auto it = user_gesture_map_.find(source_contents); | 
| + if (it != user_gesture_map_.end() && !IsExpired(it->second)) { | 
| + nav_event.is_user_initiated = true; | 
| + OnUserGestureConsumed(it->first, it->second); | 
| + } else { | 
| + nav_event.is_user_initiated = false; | 
| + } | 
| + | 
| + navigation_map_[target_url].push_back(std::move(nav_event)); | 
| +} | 
| + | 
| +// -------------------SafeBrowsingNavigationObserver Functions---------------- | 
| + | 
| +// static | 
| +const char SafeBrowsingNavigationObserver::kWebContentsUserDataKey[] = | 
| + "web_contents_safe_browsing_navigation_observer"; | 
| + | 
| +// static | 
| +void SafeBrowsingNavigationObserver::MaybeCreateForWebContents( | 
| + content::WebContents* web_contents) { | 
| + if (FromWebContents(web_contents)) | 
| + return; | 
| + // TODO(jialiul): This function will be called by TabHelpers::AttachTabHelpers | 
| + // Complete this function when the entire class is ready. | 
| +} | 
| + | 
| +// static | 
| +SafeBrowsingNavigationObserver* SafeBrowsingNavigationObserver::FromWebContents( | 
| + content::WebContents* web_contents) { | 
| + return static_cast<SafeBrowsingNavigationObserver*>( | 
| + web_contents->GetUserData(kWebContentsUserDataKey)); | 
| +} | 
| + | 
| +SafeBrowsingNavigationObserver::SafeBrowsingNavigationObserver( | 
| + content::WebContents* contents, | 
| + const scoped_refptr<SBNavigationObserverManager>& manager) | 
| + : content::WebContentsObserver(contents), | 
| + manager_(manager), | 
| + has_user_gesture_(false), | 
| + last_user_gesture_timestamp_(base::Time::Now()) {} | 
| + | 
| +SafeBrowsingNavigationObserver::~SafeBrowsingNavigationObserver() {} | 
| + | 
| +// Called when a navigation started in the WebContents. |navigation_handle| in | 
| +// paramter is unique to this navigation, which will appear in the following | 
| +// DidRedirectNavigation, and DidFinishNavigation call too. | 
| +void SafeBrowsingNavigationObserver::DidStartNavigation( | 
| + content::NavigationHandle* navigation_handle) { | 
| + // If we already seen this navigation_handle before, no need to do anything. | 
| + if (navigation_handle_map_.find(navigation_handle) != | 
| + navigation_handle_map_.end()) | 
| + return; | 
| + | 
| + // Construct a NavigationEvent based on available information in | 
| + // navigation_handle. | 
| + NavigationEvent nav_event; | 
| + content::RenderFrameHost* host = | 
| + navigation_handle->GetWebContents()->FindFrameByFrameTreeNodeId( | 
| + navigation_handle->GetFrameTreeNodeId()); | 
| 
Charlie Reis
2016/09/23 23:14:13
I'm concerned about this line, since the navigatio
 
Jialiu Lin
2016/09/27 17:50:59
Agree these are tricky cases. See my proposals bel
 | 
| + | 
| + // If there was URL previously committed in this render frame host, | 
| + // set it as the source url of this navigation. Otherwise, this is the | 
| + // first url going to commit in this frame. We set navigation_handle's URL as | 
| + // the source url. | 
| + GURL::Replacements replacements; | 
| + replacements.ClearRef(); | 
| + if (host && host->GetLastCommittedURL().is_valid()) { | 
| + nav_event.source_url = | 
| + host->GetLastCommittedURL().ReplaceComponents(replacements); | 
| 
Charlie Reis
2016/09/23 23:14:13
This looks like it won't handle different frames o
 
Jialiu Lin
2016/09/27 17:50:59
There are two cases keep me awake at night. 
(1) t
 
Charlie Reis
2016/09/28 21:08:13
Note that my example could have put evil.com in if
 
Jialiu Lin
2016/09/28 21:39:14
You're right. it is quite indirect.
 | 
| + } else { | 
| + nav_event.source_url = | 
| + navigation_handle->GetURL().ReplaceComponents(replacements); | 
| + } | 
| + | 
| + nav_event.target_url = | 
| + navigation_handle->GetURL().ReplaceComponents(replacements); | 
| + | 
| + nav_event.source_tab_id = | 
| + SessionTabHelper::IdForTab(navigation_handle->GetWebContents()); | 
| + nav_event.timestamp = base::Time::Now(); | 
| + nav_event.frame_id = navigation_handle->GetFrameTreeNodeId(); | 
| + | 
| + if (navigation_handle->IsInMainFrame()) { | 
| + nav_event.main_frame_url = nav_event.source_url; | 
| + } else { | 
| + nav_event.main_frame_url = navigation_handle->GetWebContents() | 
| + ->GetLastCommittedURL() | 
| + .ReplaceComponents(replacements); | 
| + } | 
| + if (has_user_gesture_ && !IsExpired(last_user_gesture_timestamp_)) { | 
| + nav_event.is_user_initiated = has_user_gesture_; | 
| + manager_->OnUserGestureConsumed(web_contents(), | 
| + last_user_gesture_timestamp_); | 
| + } | 
| + has_user_gesture_ = false; | 
| + navigation_handle_map_.insert( | 
| + std::make_pair(navigation_handle, std::move(nav_event))); | 
| +} | 
| + | 
| +void SafeBrowsingNavigationObserver::DidRedirectNavigation( | 
| + content::NavigationHandle* navigation_handle) { | 
| + // We should have already seen this navigation_handle in DidStartNavigation. | 
| + if (navigation_handle_map_.find(navigation_handle) == | 
| + navigation_handle_map_.end()) | 
| + return; | 
| + | 
| + NavigationEvent* nav_event = &navigation_handle_map_[navigation_handle]; | 
| + nav_event->is_server_redirect = true; | 
| + GURL::Replacements replacements; | 
| + replacements.ClearRef(); | 
| + nav_event->server_redirect_url = | 
| + navigation_handle->GetURL().ReplaceComponents(replacements); | 
| + nav_event->timestamp = base::Time::Now(); | 
| +} | 
| + | 
| +void SafeBrowsingNavigationObserver::DidFinishNavigation( | 
| + content::NavigationHandle* navigation_handle) { | 
| + if (navigation_handle_map_.find(navigation_handle) == | 
| + navigation_handle_map_.end()) | 
| + return; | 
| + | 
| + // If it is an error page, we ignore this navigation. | 
| + if (navigation_handle->IsErrorPage()) { | 
| + navigation_handle_map_.erase(navigation_handle); | 
| + return; | 
| + } | 
| + GURL::Replacements replacements; | 
| + replacements.ClearRef(); | 
| + NavigationEvent* nav_event = &navigation_handle_map_[navigation_handle]; | 
| + | 
| + // Set is_user_initiated to has_user_gesture || browser_initiated. | 
| + nav_event->is_user_initiated = | 
| + nav_event->is_user_initiated || !navigation_handle->IsRendererInitiated(); | 
| + nav_event->has_committed = navigation_handle->HasCommitted(); | 
| + nav_event->target_tab_id = | 
| + SessionTabHelper::IdForTab(navigation_handle->GetWebContents()); | 
| + nav_event->timestamp = base::Time::Now(); | 
| + | 
| + GURL nav_event_key = navigation_handle->HasCommitted() | 
| + ? navigation_handle->GetRenderFrameHost() | 
| + ->GetLastCommittedURL() | 
| + .ReplaceComponents(replacements) | 
| + : nav_event->is_server_redirect | 
| + ? nav_event->server_redirect_url | 
| + : nav_event->target_url; | 
| + manager_->RecordNavigationEvent(nav_event_key, nav_event); | 
| + navigation_handle_map_.erase(navigation_handle); | 
| +} | 
| + | 
| +void SafeBrowsingNavigationObserver::DidGetUserInteraction( | 
| + const blink::WebInputEvent::Type type) { | 
| + last_user_gesture_timestamp_ = base::Time::Now(); | 
| + has_user_gesture_ = true; | 
| + // TODO (jialiul): Refine user gesture logic when DidOpenRequestedURL | 
| + // covers all retargetting cases. | 
| + manager_->RecordUserGestureForWebContents(web_contents(), | 
| + last_user_gesture_timestamp_); | 
| +} | 
| + | 
| +void SafeBrowsingNavigationObserver::WebContentsDestroyed() { | 
| + manager_->OnWebContentDestroyed(web_contents()); | 
| + web_contents()->RemoveUserData(kWebContentsUserDataKey); | 
| + // web_contents is null after this function. | 
| +} | 
| + | 
| +} // namespace safe_browsing |