| // 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 <stddef.h> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.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_navigation_observer.h" |
| #include "chrome/browser/supervised_user/supervised_user_service.h" |
| #include "chrome/browser/supervised_user/supervised_user_service_factory.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_task_traits.h" |
| #include "content/public/browser/browser_thread.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/web_contents.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| #if defined(OS_ANDROID) |
| #include "chrome/browser/supervised_user/child_accounts/child_account_feedback_reporter_android.h" |
| #else |
| #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::WebContents; |
| |
| namespace { |
| |
| // For use in histograms. |
| enum Commands { PREVIEW, BACK, NTP, ACCESS_REQUEST, HISTOGRAM_BOUNDING_VALUE }; |
| |
| // For use in histograms.The enum values should remain synchronized with the |
| // enum ManagedUserURLRequestPermissionSource in |
| // tools/metrics/histograms/enums.xml. |
| enum class RequestPermissionSource { |
| MAIN_FRAME = 0, |
| SUB_FRAME, |
| HISTOGRAM_BOUNDING_VALUE |
| }; |
| |
| class TabCloser : public content::WebContentsUserData<TabCloser> { |
| public: |
| ~TabCloser() override {} |
| |
| static void MaybeClose(WebContents* web_contents) { |
| DCHECK(web_contents); |
| |
| // Close the tab only if there is a browser for it (which is not the case |
| // for example in a <webview>). |
| #if !defined(OS_ANDROID) |
| if (!chrome::FindBrowserWithWebContents(web_contents)) |
| return; |
| #endif |
| TabCloser::CreateForWebContents(web_contents); |
| } |
| |
| private: |
| friend class content::WebContentsUserData<TabCloser>; |
| |
| explicit TabCloser(WebContents* web_contents) : web_contents_(web_contents) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&TabCloser::CloseTabImpl, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| 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<TabCloser> weak_ptr_factory_{this}; |
| |
| WEB_CONTENTS_USER_DATA_KEY_DECL(); |
| |
| DISALLOW_COPY_AND_ASSIGN(TabCloser); |
| }; |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(TabCloser) |
| |
| // Removes all the infobars which are attached to |web_contents| and for |
| // which ShouldExpire() returns true. |
| void CleanUpInfoBar(content::WebContents* web_contents) { |
| InfoBarService* service = InfoBarService::FromWebContents(web_contents); |
| if (service) { |
| content::LoadCommittedDetails details; |
| // |details.is_same_document| 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()); |
| content::NavigationController& controller = web_contents->GetController(); |
| details.entry = controller.GetVisibleEntry(); |
| 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); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<SupervisedUserInterstitial> SupervisedUserInterstitial::Create( |
| WebContents* web_contents, |
| const GURL& url, |
| supervised_user_error_page::FilteringBehaviorReason reason, |
| int frame_id, |
| int64_t interstitial_navigation_id) { |
| std::unique_ptr<SupervisedUserInterstitial> interstitial = |
| base::WrapUnique(new SupervisedUserInterstitial( |
| web_contents, url, reason, frame_id, interstitial_navigation_id)); |
| |
| if (web_contents->GetMainFrame()->GetFrameTreeNodeId() == frame_id) |
| CleanUpInfoBar(web_contents); |
| |
| // Caller is responsible for deleting the interstitial. |
| return interstitial; |
| } |
| |
| SupervisedUserInterstitial::SupervisedUserInterstitial( |
| WebContents* web_contents, |
| const GURL& url, |
| supervised_user_error_page::FilteringBehaviorReason reason, |
| int frame_id, |
| int64_t interstitial_navigation_id) |
| : web_contents_(web_contents), |
| profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())), |
| url_(url), |
| reason_(reason), |
| frame_id_(frame_id), |
| interstitial_navigation_id_(interstitial_navigation_id) {} |
| |
| SupervisedUserInterstitial::~SupervisedUserInterstitial() {} |
| |
| // static |
| std::string SupervisedUserInterstitial::GetHTMLContents( |
| Profile* profile, |
| supervised_user_error_page::FilteringBehaviorReason reason, |
| bool already_sent_request, |
| bool is_main_frame) { |
| bool is_child_account = profile->IsChild(); |
| |
| bool is_deprecated = !is_child_account; |
| |
| SupervisedUserService* supervised_user_service = |
| SupervisedUserServiceFactory::GetForProfile(profile); |
| |
| std::string custodian = supervised_user_service->GetCustodianName(); |
| std::string second_custodian = |
| supervised_user_service->GetSecondCustodianName(); |
| std::string custodian_email = |
| supervised_user_service->GetCustodianEmailAddress(); |
| std::string second_custodian_email = |
| 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, is_deprecated, reason, |
| g_browser_process->GetApplicationLocale(), already_sent_request, |
| is_main_frame); |
| } |
| |
| void SupervisedUserInterstitial::GoBack() { |
| // GoBack only for main frame. |
| DCHECK_EQ(web_contents()->GetMainFrame()->GetFrameTreeNodeId(), frame_id()); |
| |
| UMA_HISTOGRAM_ENUMERATION("ManagedMode.BlockingInterstitialCommand", BACK, |
| HISTOGRAM_BOUNDING_VALUE); |
| AttemptMoveAwayFromCurrentFrameURL(); |
| OnInterstitialDone(); |
| } |
| |
| void SupervisedUserInterstitial::RequestPermission( |
| base::OnceCallback<void(bool)> RequestCallback) { |
| UMA_HISTOGRAM_ENUMERATION("ManagedMode.BlockingInterstitialCommand", |
| ACCESS_REQUEST, HISTOGRAM_BOUNDING_VALUE); |
| |
| RequestPermissionSource source; |
| if (web_contents()->GetMainFrame()->GetFrameTreeNodeId() == frame_id()) |
| source = RequestPermissionSource::MAIN_FRAME; |
| else |
| source = RequestPermissionSource::SUB_FRAME; |
| |
| UMA_HISTOGRAM_ENUMERATION("ManagedUsers.RequestPermissionSource", source, |
| RequestPermissionSource::HISTOGRAM_BOUNDING_VALUE); |
| |
| SupervisedUserService* supervised_user_service = |
| SupervisedUserServiceFactory::GetForProfile(profile_); |
| supervised_user_service->AddURLAccessRequest(url_, |
| std::move(RequestCallback)); |
| } |
| |
| void SupervisedUserInterstitial::ShowFeedback() { |
| // TODO(yilkal): Remove checking IsChild since legacy supervised users are |
| // deprecated. |
| bool is_child_account = profile_->IsChild(); |
| |
| SupervisedUserService* supervised_user_service = |
| SupervisedUserServiceFactory::GetForProfile(profile_); |
| std::string second_custodian = |
| supervised_user_service->GetSecondCustodianName(); |
| |
| base::string16 reason = |
| l10n_util::GetStringUTF16(supervised_user_error_page::GetBlockMessageID( |
| reason_, is_child_account, second_custodian.empty())); |
| std::string message = l10n_util::GetStringFUTF8( |
| IDS_BLOCK_INTERSTITIAL_DEFAULT_FEEDBACK_TEXT, reason); |
| #if defined(OS_ANDROID) |
| DCHECK(is_child_account); |
| ReportChildAccountFeedback(web_contents_, message, url_); |
| #else |
| chrome::ShowFeedbackPage( |
| url_, profile_, chrome::kFeedbackSourceSupervisedUserInterstitial, |
| message, std::string() /* description_placeholder_text */, |
| std::string() /* category_tag */, std::string() /* extra_diagnostics */); |
| #endif |
| return; |
| } |
| |
| void SupervisedUserInterstitial::AttemptMoveAwayFromCurrentFrameURL() { |
| // No need to do anything if the WebContents is in the process of being |
| // destroyed anyway. |
| if (web_contents_->IsBeingDestroyed()) |
| return; |
| |
| // If the interstitial was shown over an existing page, navigate back from |
| // that page. If that is not possible, attempt to close the entire tab. |
| if (web_contents_->GetController().CanGoBack()) { |
| web_contents_->GetController().GoBack(); |
| return; |
| } |
| |
| TabCloser::MaybeClose(web_contents_); |
| } |
| |
| void SupervisedUserInterstitial::OnInterstitialDone() { |
| auto* navigation_observer = |
| SupervisedUserNavigationObserver::FromWebContents(web_contents_); |
| |
| // After this, the WebContents may be destroyed. Make sure we don't try to use |
| // it again. |
| web_contents_ = nullptr; |
| navigation_observer->OnInterstitialDone(frame_id_); |
| } |