blob: f978967303e2a0d4cfd0e8f5ffa993bbc03b2205 [file] [log] [blame]
// Copyright 2014 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/supervised_user/supervised_user_navigation_observer.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/supervised_user/supervised_user_interstitial.h"
#include "chrome/browser/supervised_user/supervised_user_navigation_throttle.h"
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/supervised_user/supervised_user_url_filter.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "components/history/content/browser/history_context_helper.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/sessions/content/content_serialized_navigation_builder.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/reload_type.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
namespace {
bool IsMainFrame(content::RenderFrameHost* render_frame_host) {
return !render_frame_host->GetParent();
}
} // namespace
using content::NavigationEntry;
SupervisedUserNavigationObserver::~SupervisedUserNavigationObserver() {
supervised_user_service_->RemoveObserver(this);
}
SupervisedUserNavigationObserver::SupervisedUserNavigationObserver(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
receiver_(web_contents, this) {
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
supervised_user_service_ =
SupervisedUserServiceFactory::GetForProfile(profile);
url_filter_ = supervised_user_service_->GetURLFilter();
supervised_user_service_->AddObserver(this);
}
// static
void SupervisedUserNavigationObserver::OnRequestBlocked(
content::WebContents* web_contents,
const GURL& url,
supervised_user_error_page::FilteringBehaviorReason reason,
int64_t navigation_id,
int frame_id,
const OnInterstitialResultCallback& callback) {
SupervisedUserNavigationObserver* navigation_observer =
SupervisedUserNavigationObserver::FromWebContents(web_contents);
// Cancel the navigation if there is no navigation observer.
if (!navigation_observer) {
callback.Run(
SupervisedUserNavigationThrottle::CallbackActions::kCancelNavigation,
/* already_requested_permission */ false, /* is_main_frame */ false);
return;
}
navigation_observer->OnRequestBlockedInternal(url, reason, navigation_id,
frame_id, callback);
}
void SupervisedUserNavigationObserver::UpdateMainFrameFilteringStatus(
SupervisedUserURLFilter::FilteringBehavior behavior,
supervised_user_error_page::FilteringBehaviorReason reason) {
main_frame_filtering_behavior_ = behavior;
main_frame_filtering_behavior_reason_ = reason;
}
void SupervisedUserNavigationObserver::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted())
return;
int frame_id = navigation_handle->GetFrameTreeNodeId();
int64_t navigation_id = navigation_handle->GetNavigationId();
// If this is a different navigation than the one that triggered the
// interstitial in the frame, then interstitial is done.
if (base::Contains(supervised_user_interstitials_, frame_id) &&
navigation_id != supervised_user_interstitials_[frame_id]
->interstitial_navigation_id()) {
OnInterstitialDone(frame_id);
}
// Only filter same page navigations (eg. pushState/popState); others will
// have been filtered by the NavigationThrottle.
if (navigation_handle->IsSameDocument() &&
navigation_handle->IsInMainFrame()) {
auto* render_frame_host = web_contents()->GetMainFrame();
int process_id = render_frame_host->GetProcess()->GetID();
int routing_id = render_frame_host->GetRoutingID();
bool skip_manual_parent_filter =
url_filter_->ShouldSkipParentManualAllowlistFiltering(web_contents());
url_filter_->GetFilteringBehaviorForURLWithAsyncChecks(
web_contents()->GetLastCommittedURL(),
base::BindOnce(
&SupervisedUserNavigationObserver::URLFilterCheckCallback,
weak_ptr_factory_.GetWeakPtr(), navigation_handle->GetURL(),
process_id, routing_id),
skip_manual_parent_filter);
}
}
void SupervisedUserNavigationObserver::FrameDeleted(
content::RenderFrameHost* render_frame_host) {
int frame_id = render_frame_host->GetFrameTreeNodeId();
supervised_user_interstitials_.erase(frame_id);
}
void SupervisedUserNavigationObserver::DidFinishLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
if (IsMainFrame(render_frame_host)) {
bool main_frame_blocked =
base::Contains(supervised_user_interstitials_,
render_frame_host->GetFrameTreeNodeId());
int count = supervised_user_interstitials_.size();
if (main_frame_blocked)
count = 0;
UMA_HISTOGRAM_COUNTS_1000("ManagedUsers.BlockedIframeCount", count);
}
if (base::Contains(supervised_user_interstitials_,
render_frame_host->GetFrameTreeNodeId())) {
UMA_HISTOGRAM_COUNTS_1000("ManagedUsers.BlockedFrameDepth",
render_frame_host->GetFrameDepth());
}
}
void SupervisedUserNavigationObserver::OnURLFilterChanged() {
auto* main_frame = web_contents()->GetMainFrame();
int main_frame_process_id = main_frame->GetProcess()->GetID();
int routing_id = main_frame->GetRoutingID();
bool skip_manual_parent_filter =
url_filter_->ShouldSkipParentManualAllowlistFiltering(web_contents());
url_filter_->GetFilteringBehaviorForURLWithAsyncChecks(
web_contents()->GetLastCommittedURL(),
base::BindOnce(&SupervisedUserNavigationObserver::URLFilterCheckCallback,
weak_ptr_factory_.GetWeakPtr(),
web_contents()->GetLastCommittedURL(),
main_frame_process_id, routing_id),
skip_manual_parent_filter);
MaybeUpdateRequestedHosts();
Profile* profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
SupervisedUserService* service =
SupervisedUserServiceFactory::GetForProfile(profile);
if (!service->IsSupervisedUserIframeFilterEnabled())
return;
// Iframe filtering has been enabled.
web_contents()->ForEachFrame(
base::BindRepeating(&SupervisedUserNavigationObserver::FilterRenderFrame,
weak_ptr_factory_.GetWeakPtr()));
}
void SupervisedUserNavigationObserver::OnInterstitialDone(int frame_id) {
supervised_user_interstitials_.erase(frame_id);
}
void SupervisedUserNavigationObserver::OnRequestBlockedInternal(
const GURL& url,
supervised_user_error_page::FilteringBehaviorReason reason,
int64_t navigation_id,
int frame_id,
const OnInterstitialResultCallback& callback) {
// TODO(bauerb): Use SaneTime when available.
base::Time timestamp = base::Time::Now();
// Create a history entry for the attempt and mark it as such. This history
// entry should be marked as "not hidden" so the user can see attempted but
// blocked navigations. (This is in contrast to the normal behavior, wherein
// Chrome marks navigations that result in an error as hidden.) This is to
// show the user the same thing that the custodian will see on the dashboard
// (where it gets via a different mechanism unrelated to history).
history::HistoryAddPageArgs add_page_args(
url, timestamp, history::ContextIDForWebContents(web_contents()),
/*nav_entry_id=*/0, /*referrer=*/url, history::RedirectList(),
ui::PAGE_TRANSITION_BLOCKED, /*hidden=*/false, history::SOURCE_BROWSED,
/*did_replace_entry=*/false, /*consider_for_ntp_most_visited=*/true,
/*publicly_routable=*/false);
// Add the entry to the history database.
Profile* profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(profile,
ServiceAccessType::IMPLICIT_ACCESS);
// |history_service| is null if saving history is disabled.
if (history_service)
history_service->AddPage(add_page_args);
std::unique_ptr<NavigationEntry> entry = NavigationEntry::Create();
entry->SetVirtualURL(url);
entry->SetTimestamp(timestamp);
auto serialized_entry = std::make_unique<sessions::SerializedNavigationEntry>(
sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
blocked_navigations_.size(), entry.get()));
blocked_navigations_.push_back(std::move(serialized_entry));
// Show the interstitial.
const bool initial_page_load = true;
MaybeShowInterstitial(url, reason, initial_page_load, navigation_id, frame_id,
callback);
}
void SupervisedUserNavigationObserver::URLFilterCheckCallback(
const GURL& url,
int render_frame_process_id,
int render_frame_routing_id,
SupervisedUserURLFilter::FilteringBehavior behavior,
supervised_user_error_page::FilteringBehaviorReason reason,
bool uncertain) {
auto* render_frame_host = content::RenderFrameHost::FromID(
render_frame_process_id, render_frame_routing_id);
if (!render_frame_host || !render_frame_host->IsRenderFrameLive())
return;
int frame_id = render_frame_host->GetFrameTreeNodeId();
bool is_showing_interstitial =
base::Contains(supervised_user_interstitials_, frame_id);
bool should_show_interstitial =
behavior == SupervisedUserURLFilter::FilteringBehavior::BLOCK;
// If an interstitial is being shown where it shouldn't (for e.g. because a
// parent just approved a request) reloading will clear it. On the other hand,
// if an interstitial error page is not being shown but it should be shown,
// then reloading will trigger the navigation throttle to show the error page.
if (is_showing_interstitial != should_show_interstitial) {
if (IsMainFrame(render_frame_host)) {
web_contents()->GetController().Reload(content::ReloadType::NORMAL,
/* check_for_repost */ false);
return;
}
render_frame_host->Reload();
}
}
void SupervisedUserNavigationObserver::MaybeShowInterstitial(
const GURL& url,
supervised_user_error_page::FilteringBehaviorReason reason,
bool initial_page_load,
int64_t navigation_id,
int frame_id,
const OnInterstitialResultCallback& callback) {
std::unique_ptr<SupervisedUserInterstitial> interstitial =
SupervisedUserInterstitial::Create(web_contents(), url, reason, frame_id,
navigation_id);
supervised_user_interstitials_[frame_id] = std::move(interstitial);
bool already_requested = base::Contains(requested_hosts_, url.host());
bool is_main_frame =
frame_id == web_contents()->GetMainFrame()->GetFrameTreeNodeId();
callback.Run(SupervisedUserNavigationThrottle::CallbackActions::
kCancelWithInterstitial,
already_requested, is_main_frame);
}
void SupervisedUserNavigationObserver::FilterRenderFrame(
content::RenderFrameHost* render_frame_host) {
// If the RenderFrameHost is not live return.
// If the RenderFrameHost belongs to the main frame, return. This is because
// the main frame is already filtered in
// |SupervisedUserNavigationObserver::OnURLFilterChanged|.
if (!render_frame_host->IsRenderFrameLive() || IsMainFrame(render_frame_host))
return;
const GURL& last_committed_url = render_frame_host->GetLastCommittedURL();
bool skip_manual_parent_filter =
url_filter_->ShouldSkipParentManualAllowlistFiltering(web_contents());
url_filter_->GetFilteringBehaviorForURLWithAsyncChecks(
web_contents()->GetLastCommittedURL(),
base::BindOnce(&SupervisedUserNavigationObserver::URLFilterCheckCallback,
weak_ptr_factory_.GetWeakPtr(), last_committed_url,
render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID()),
skip_manual_parent_filter);
}
void SupervisedUserNavigationObserver::GoBack() {
auto* render_frame_host = receiver_.GetCurrentTargetFrame();
auto id = render_frame_host->GetFrameTreeNodeId();
// Request can come only from the main frame.
if (!IsMainFrame(render_frame_host))
return;
if (base::Contains(supervised_user_interstitials_, id))
supervised_user_interstitials_[id]->GoBack();
}
void SupervisedUserNavigationObserver::RequestPermission(
RequestPermissionCallback callback) {
auto* render_frame_host = receiver_.GetCurrentTargetFrame();
int id = render_frame_host->GetFrameTreeNodeId();
if (base::Contains(supervised_user_interstitials_, id)) {
SupervisedUserInterstitial* interstitial =
supervised_user_interstitials_[id].get();
interstitial->RequestPermission(
base::BindOnce(&SupervisedUserNavigationObserver::RequestCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
interstitial->url().host()));
}
}
void SupervisedUserNavigationObserver::Feedback() {
auto* render_frame_host = receiver_.GetCurrentTargetFrame();
int id = render_frame_host->GetFrameTreeNodeId();
if (base::Contains(supervised_user_interstitials_, id))
supervised_user_interstitials_[id]->ShowFeedback();
}
void SupervisedUserNavigationObserver::RequestCreated(
RequestPermissionCallback callback,
const std::string& host,
bool successfully_created_request) {
if (successfully_created_request)
requested_hosts_.insert(host);
std::move(callback).Run(successfully_created_request);
}
void SupervisedUserNavigationObserver::MaybeUpdateRequestedHosts() {
SupervisedUserURLFilter::FilteringBehavior filtering_behavior;
for (auto iter = requested_hosts_.begin(); iter != requested_hosts_.end();) {
bool is_manual = url_filter_->GetManualFilteringBehaviorForURL(
GURL(*iter), &filtering_behavior);
if (is_manual && filtering_behavior ==
SupervisedUserURLFilter::FilteringBehavior::ALLOW) {
iter = requested_hosts_.erase(iter);
} else {
iter++;
}
}
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(SupervisedUserNavigationObserver)