// 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_interstitial.h" #include #include "base/memory/weak_ptr.h" #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/supervised_user/supervised_user_service.h" #include "chrome/browser/supervised_user/supervised_user_service_factory.h" #include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" #include "components/infobars/core/infobar.h" #include "components/infobars/core/infobar_delegate.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/interstitial_page.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_user_data.h" #include "content/public/browser/web_ui.h" #include "grit/browser_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/webui/jstemplate_builder.h" #include "ui/base/webui/web_ui_util.h" #if BUILDFLAG(ANDROID_JAVA_UI) #include "chrome/browser/supervised_user/child_accounts/child_account_feedback_reporter_android.h" #elif !defined(OS_ANDROID) #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #endif using content::BrowserThread; using content::WebContents; namespace { class TabCloser : public content::WebContentsUserData { public: static void MaybeClose(WebContents* web_contents) { // Close the tab if there is no history entry to go back to and there is a // browser for the tab (which is not the case for example in a ). if (!web_contents->GetController().IsInitialBlankNavigation()) return; #if !defined(OS_ANDROID) if (!chrome::FindBrowserWithWebContents(web_contents)) return; #endif TabCloser::CreateForWebContents(web_contents); } private: friend class content::WebContentsUserData; explicit TabCloser(WebContents* web_contents) : web_contents_(web_contents), weak_ptr_factory_(this) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&TabCloser::CloseTabImpl, weak_ptr_factory_.GetWeakPtr())); } ~TabCloser() override {} void CloseTabImpl() { // On Android, FindBrowserWithWebContents and TabStripModel don't exist. #if !defined(OS_ANDROID) Browser* browser = chrome::FindBrowserWithWebContents(web_contents_); DCHECK(browser); TabStripModel* tab_strip = browser->tab_strip_model(); DCHECK_NE(TabStripModel::kNoTab, tab_strip->GetIndexOfWebContents(web_contents_)); if (tab_strip->count() <= 1) { // Don't close the last tab in the window. web_contents_->RemoveUserData(UserDataKey()); return; } #endif web_contents_->Close(); } WebContents* web_contents_; base::WeakPtrFactory weak_ptr_factory_; }; } // namespace DEFINE_WEB_CONTENTS_USER_DATA_KEY(TabCloser); content::InterstitialPageDelegate::TypeID SupervisedUserInterstitial::kTypeForTesting = &SupervisedUserInterstitial::kTypeForTesting; // static void SupervisedUserInterstitial::Show( WebContents* web_contents, const GURL& url, supervised_user_error_page::FilteringBehaviorReason reason, const base::Callback& callback) { SupervisedUserInterstitial* interstitial = new SupervisedUserInterstitial(web_contents, url, reason, callback); // If Init() does not complete fully, immediately delete the interstitial. if (!interstitial->Init()) delete interstitial; // Otherwise |interstitial_page_| is responsible for deleting it. } SupervisedUserInterstitial::SupervisedUserInterstitial( WebContents* web_contents, const GURL& url, supervised_user_error_page::FilteringBehaviorReason reason, const base::Callback& callback) : web_contents_(web_contents), profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())), interstitial_page_(NULL), url_(url), reason_(reason), callback_(callback), weak_ptr_factory_(this) {} SupervisedUserInterstitial::~SupervisedUserInterstitial() { DCHECK(!web_contents_); } bool SupervisedUserInterstitial::Init() { if (ShouldProceed()) { // It can happen that the site was only allowed very recently and the URL // filter on the IO thread had not been updated yet. Proceed with the // request without showing the interstitial. DispatchContinueRequest(true); return false; } InfoBarService* service = InfoBarService::FromWebContents(web_contents_); if (service) { // Remove all the infobars which are attached to |web_contents_| and for // which ShouldExpire() returns true. content::LoadCommittedDetails details; // |details.is_in_page| is default false, and |details.is_main_frame| is // default true. This results in is_navigation_to_different_page() returning // true. DCHECK(details.is_navigation_to_different_page()); const content::NavigationController& controller = web_contents_->GetController(); details.entry = controller.GetActiveEntry(); if (controller.GetLastCommittedEntry()) { details.previous_entry_index = controller.GetLastCommittedEntryIndex(); details.previous_url = controller.GetLastCommittedEntry()->GetURL(); } details.type = content::NAVIGATION_TYPE_NEW_PAGE; for (int i = service->infobar_count() - 1; i >= 0; --i) { infobars::InfoBar* infobar = service->infobar_at(i); if (infobar->delegate()->ShouldExpire( InfoBarService::NavigationDetailsFromLoadCommittedDetails( details))) service->RemoveInfoBar(infobar); } } SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile_); supervised_user_service->AddObserver(this); interstitial_page_ = content::InterstitialPage::Create(web_contents_, true, url_, this); interstitial_page_->Show(); return true; } // static std::string SupervisedUserInterstitial::GetHTMLContents( Profile* profile, supervised_user_error_page::FilteringBehaviorReason reason) { bool is_child_account = profile->IsChild(); SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile); base::string16 custodian = base::UTF8ToUTF16(supervised_user_service->GetCustodianName()); base::string16 second_custodian = base::UTF8ToUTF16(supervised_user_service->GetSecondCustodianName()); base::string16 custodian_email = base::UTF8ToUTF16(supervised_user_service->GetCustodianEmailAddress()); base::string16 second_custodian_email = base::UTF8ToUTF16( supervised_user_service->GetSecondCustodianEmailAddress()); std::string profile_image_url = profile->GetPrefs()->GetString( prefs::kSupervisedUserCustodianProfileImageURL); std::string profile_image_url2 = profile->GetPrefs()->GetString( prefs::kSupervisedUserSecondCustodianProfileImageURL); bool allow_access_requests = supervised_user_service->AccessRequestsEnabled(); return supervised_user_error_page::BuildHtml( allow_access_requests, profile_image_url, profile_image_url2, custodian, custodian_email, second_custodian, second_custodian_email, is_child_account, reason, g_browser_process->GetApplicationLocale()); } std::string SupervisedUserInterstitial::GetHTMLContents() { return GetHTMLContents(profile_, reason_); } void SupervisedUserInterstitial::CommandReceived(const std::string& command) { // For use in histograms. enum Commands { PREVIEW, BACK, NTP, ACCESS_REQUEST, HISTOGRAM_BOUNDING_VALUE }; if (command == "\"back\"") { UMA_HISTOGRAM_ENUMERATION("ManagedMode.BlockingInterstitialCommand", BACK, HISTOGRAM_BOUNDING_VALUE); // The interstitial's reference to the WebContents will go away after the // DontProceed call. WebContents* web_contents = web_contents_; DCHECK(web_contents->GetController().GetTransientEntry()); interstitial_page_->DontProceed(); TabCloser::MaybeClose(web_contents); return; } if (command == "\"request\"") { UMA_HISTOGRAM_ENUMERATION("ManagedMode.BlockingInterstitialCommand", ACCESS_REQUEST, HISTOGRAM_BOUNDING_VALUE); SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile_); supervised_user_service->AddURLAccessRequest( url_, base::Bind(&SupervisedUserInterstitial::OnAccessRequestAdded, weak_ptr_factory_.GetWeakPtr())); return; } SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile_); base::string16 second_custodian = base::UTF8ToUTF16(supervised_user_service->GetSecondCustodianName()); if (command == "\"feedback\"") { base::string16 reason = l10n_util::GetStringUTF16(supervised_user_error_page::GetBlockMessageID( reason_, true, second_custodian.empty())); std::string message = l10n_util::GetStringFUTF8( IDS_BLOCK_INTERSTITIAL_DEFAULT_FEEDBACK_TEXT, reason); #if BUILDFLAG(ANDROID_JAVA_UI) ReportChildAccountFeedback(web_contents_, message, url_); #else chrome::ShowFeedbackPage(chrome::FindBrowserWithWebContents(web_contents_), message, std::string()); #endif return; } NOTREACHED(); } void SupervisedUserInterstitial::OnProceed() { DispatchContinueRequest(true); } void SupervisedUserInterstitial::OnDontProceed() { DispatchContinueRequest(false); } content::InterstitialPageDelegate::TypeID SupervisedUserInterstitial::GetTypeForTesting() const { return SupervisedUserInterstitial::kTypeForTesting; } void SupervisedUserInterstitial::OnURLFilterChanged() { if (ShouldProceed()) interstitial_page_->Proceed(); } void SupervisedUserInterstitial::OnAccessRequestAdded(bool success) { VLOG(1) << "Sent access request for " << url_.spec() << (success ? " successfully" : " unsuccessfully"); std::string jsFunc = base::StringPrintf("setRequestStatus(%s);", success ? "true" : "false"); interstitial_page_->GetMainFrame()->ExecuteJavaScript( base::ASCIIToUTF16(jsFunc)); } bool SupervisedUserInterstitial::ShouldProceed() { SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile_); SupervisedUserURLFilter* url_filter = supervised_user_service->GetURLFilterForUIThread(); SupervisedUserURLFilter::FilteringBehavior behavior; if (url_filter->HasAsyncURLChecker()) { if (!url_filter->GetManualFilteringBehaviorForURL(url_, &behavior)) return false; } else { behavior = url_filter->GetFilteringBehaviorForURL(url_); } return behavior != SupervisedUserURLFilter::BLOCK; } void SupervisedUserInterstitial::DispatchContinueRequest( bool continue_request) { SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile_); supervised_user_service->RemoveObserver(this); if (!callback_.is_null()) BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(callback_, continue_request)); // After this, the WebContents may be destroyed. Make sure we don't try to use // it again. web_contents_ = NULL; }