| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/security_interstitials/content/security_interstitial_tab_helper.h" |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "components/security_interstitials/content/security_interstitial_page.h" |
| #include "components/security_interstitials/core/controller_client.h" |
| #include "content/public/browser/frame_tree_node_id.h" |
| #include "content/public/browser/navigation_handle.h" |
| |
| namespace { |
| |
| bool FrameTypeMayTriggerInterstitial( |
| content::NavigationHandle* navigation_handle) { |
| return (navigation_handle->GetNavigatingFrameType() == |
| content::FrameType::kPrimaryMainFrame || |
| navigation_handle->IsGuestViewMainFrame() || |
| (navigation_handle->GetNavigatingFrameType() == |
| content::FrameType::kSubframe && |
| navigation_handle->GetParentFrame()->IsActive())); |
| } |
| |
| } // namespace |
| namespace security_interstitials { |
| |
| SecurityInterstitialTabHelper::~SecurityInterstitialTabHelper() = default; |
| |
| void SecurityInterstitialTabHelper::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (navigation_handle->IsSameDocument() || |
| !FrameTypeMayTriggerInterstitial(navigation_handle)) { |
| return; |
| } |
| |
| int64_t navigation_id = navigation_handle->GetNavigationId(); |
| content::FrameTreeNodeId frame_tree_node_id = |
| navigation_handle->GetFrameTreeNodeId(); |
| if (navigation_handle->HasCommitted()) { |
| // Prepare any existing interstitial in this frame for removal. |
| if (IsInterstitialCommittedForFrame(frame_tree_node_id)) { |
| base::UmaHistogramEnumeration("interstitial.CloseReason", |
| InterstitialCloseReason::NAVIGATE_AWAY); |
| blocking_documents_for_committed_navigations_.find(frame_tree_node_id) |
| ->second->OnInterstitialClosing(); |
| } |
| |
| if (IsInterstitialPendingForNavigation(navigation_id)) { |
| blocking_documents_for_committed_navigations_.insert_or_assign( |
| frame_tree_node_id, |
| std::move( |
| blocking_documents_for_pending_navigations_[navigation_id])); |
| // The navigation has been committed, update interstitial state to |
| // reflect the interstitial is being shown. |
| base::UmaHistogramEnumeration( |
| "interstitial.CloseReason", |
| InterstitialCloseReason::INTERSTITIAL_SHOWN); |
| blocking_documents_for_committed_navigations_.find(frame_tree_node_id) |
| ->second->OnInterstitialShown(); |
| blocking_documents_for_pending_navigations_.erase(navigation_id); |
| } else { |
| // There is no new interstitial for navigation_id, so clear any previously |
| // committed blocking document in the same frame. |
| blocking_documents_for_committed_navigations_.erase(frame_tree_node_id); |
| } |
| } else if (IsInterstitialPendingForNavigation(navigation_id)) { |
| // Stop tracking the associated navigation since it has not committed. |
| blocking_documents_for_pending_navigations_.erase(navigation_id); |
| } |
| |
| // Interstitials may change the visibility of the URL or other security state. |
| web_contents()->DidChangeVisibleSecurityState(); |
| } |
| |
| void SecurityInterstitialTabHelper::WebContentsDestroyed() { |
| content::FrameTreeNodeId main_frame_tree_node_id = |
| web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId(); |
| // Record a tab closing event if the main frame is displaying an interstitial. |
| if (IsInterstitialCommittedForFrame(main_frame_tree_node_id)) { |
| base::UmaHistogramEnumeration("interstitial.CloseReason", |
| InterstitialCloseReason::CLOSE_TAB); |
| blocking_documents_for_committed_navigations_.find(main_frame_tree_node_id) |
| ->second->OnInterstitialClosing(); |
| } |
| blocking_documents_for_committed_navigations_.clear(); |
| } |
| |
| void SecurityInterstitialTabHelper::FrameDeleted( |
| content::FrameTreeNodeId frame_tree_node_id) { |
| content::FrameTreeNodeId main_frame_tree_node_id = |
| web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId(); |
| // Primary main frame interstitial closing is processed on |
| // WebContentsDestroyed. |
| // TODO(crbug.com/370683964): Investigate assumption in safe browsing that |
| // requires main frame interstitials to be cleared in WebContentsDestroyed. |
| if (!IsInterstitialCommittedForFrame(frame_tree_node_id) || |
| main_frame_tree_node_id == frame_tree_node_id) { |
| return; |
| } |
| blocking_documents_for_committed_navigations_.find(frame_tree_node_id) |
| ->second->OnInterstitialClosing(); |
| blocking_documents_for_committed_navigations_.erase(frame_tree_node_id); |
| } |
| |
| // static |
| void SecurityInterstitialTabHelper::AssociateBlockingPage( |
| content::NavigationHandle* navigation_handle, |
| std::unique_ptr<security_interstitials::SecurityInterstitialPage> |
| blocking_page) { |
| // An interstitial should not be shown in a prerendered page or in a fenced |
| // frame. The prerender should just be canceled. |
| CHECK(FrameTypeMayTriggerInterstitial(navigation_handle)); |
| |
| // CreateForWebContents() creates a tab helper if it doesn't yet exist for the |
| // WebContents provided by |navigation_handle|. |
| auto* web_contents = navigation_handle->GetWebContents(); |
| SecurityInterstitialTabHelper::CreateForWebContents(web_contents); |
| |
| SecurityInterstitialTabHelper* helper = |
| SecurityInterstitialTabHelper::FromWebContents(web_contents); |
| helper->SetBlockingPage(navigation_handle->GetNavigationId(), |
| std::move(blocking_page)); |
| } |
| |
| // static |
| void SecurityInterstitialTabHelper::BindInterstitialCommands( |
| mojo::PendingAssociatedReceiver< |
| security_interstitials::mojom::InterstitialCommands> receiver, |
| content::RenderFrameHost* rfh) { |
| auto* web_contents = content::WebContents::FromRenderFrameHost(rfh); |
| if (!web_contents) { |
| return; |
| } |
| auto* tab_helper = |
| SecurityInterstitialTabHelper::FromWebContents(web_contents); |
| if (!tab_helper) { |
| return; |
| } |
| tab_helper->receivers_.Bind(rfh, std::move(receiver)); |
| } |
| |
| bool SecurityInterstitialTabHelper::IsInterstitialPendingForNavigation( |
| int64_t navigation_id) const { |
| return base::Contains(blocking_documents_for_pending_navigations_, |
| navigation_id); |
| } |
| |
| bool SecurityInterstitialTabHelper::ShouldDisplayURL() const { |
| SecurityInterstitialPage* blocking_page = GetBlockingPageForMainFrame(); |
| // If the main frame does not display a blocking interstitial then default to |
| // being able to display the URL. |
| return blocking_page ? blocking_page->ShouldDisplayURL() : true; |
| } |
| |
| bool SecurityInterstitialTabHelper::IsDisplayingInterstitial() const { |
| return !blocking_documents_for_committed_navigations_.empty(); |
| } |
| |
| bool SecurityInterstitialTabHelper::HasPendingOrActiveInterstitial() const { |
| return !blocking_documents_for_pending_navigations_.empty() || |
| IsDisplayingInterstitial(); |
| } |
| |
| bool SecurityInterstitialTabHelper::IsInterstitialCommittedForFrame( |
| content::FrameTreeNodeId frame_tree_node_id) const { |
| return base::Contains(blocking_documents_for_committed_navigations_, |
| frame_tree_node_id); |
| } |
| |
| security_interstitials::SecurityInterstitialPage* |
| SecurityInterstitialTabHelper::GetBlockingPageForFrame( |
| content::FrameTreeNodeId frame_tree_node_id) { |
| if (IsInterstitialCommittedForFrame(frame_tree_node_id)) { |
| return blocking_documents_for_committed_navigations_ |
| .find(frame_tree_node_id) |
| ->second.get(); |
| } |
| return nullptr; |
| } |
| |
| security_interstitials::SecurityInterstitialPage* |
| SecurityInterstitialTabHelper:: |
| GetBlockingPageForCurrentlyCommittedNavigationForTesting() { |
| // TODO(crbug.com/369759355): Support retrieving blocking page by frame ID. |
| content::FrameTreeNodeId frame_tree_node_id = |
| web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId(); |
| if (IsInterstitialCommittedForFrame(frame_tree_node_id)) { |
| return blocking_documents_for_committed_navigations_ |
| .find(frame_tree_node_id) |
| ->second.get(); |
| } |
| return nullptr; |
| } |
| |
| SecurityInterstitialTabHelper::SecurityInterstitialTabHelper( |
| content::WebContents* web_contents) |
| : WebContentsObserver(web_contents), |
| content::WebContentsUserData<SecurityInterstitialTabHelper>( |
| *web_contents), |
| receivers_(web_contents, this) {} |
| |
| void SecurityInterstitialTabHelper::SetBlockingPage( |
| int64_t navigation_id, |
| std::unique_ptr<security_interstitials::SecurityInterstitialPage> |
| blocking_page) { |
| blocking_documents_for_pending_navigations_[navigation_id] = |
| std::move(blocking_page); |
| } |
| |
| SecurityInterstitialPage* |
| SecurityInterstitialTabHelper::GetBlockingPageForCurrentTargetFrame() { |
| auto* render_frame_host = receivers_.GetCurrentTargetFrame(); |
| content::FrameTreeNodeId id = render_frame_host->GetFrameTreeNodeId(); |
| if (!IsInterstitialCommittedForFrame(id)) { |
| // TODO(crbug.com/376688788): Remove this condition. This method should not |
| // be invoked if there is no blocking page for the current target frame. |
| return nullptr; |
| } |
| return blocking_documents_for_committed_navigations_.find(id)->second.get(); |
| } |
| |
| SecurityInterstitialPage* |
| SecurityInterstitialTabHelper::GetBlockingPageForMainFrame() const { |
| content::FrameTreeNodeId frame_tree_node_id = |
| web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId(); |
| if (IsInterstitialCommittedForFrame(frame_tree_node_id)) { |
| return blocking_documents_for_committed_navigations_ |
| .find(frame_tree_node_id) |
| ->second.get(); |
| } |
| return nullptr; |
| } |
| |
| void SecurityInterstitialTabHelper::HandleCommand( |
| security_interstitials::SecurityInterstitialCommand cmd) { |
| // TODO(crbug.com/369784619): Currently commands need to be converted to |
| // strings before passing them to CommandReceived, which then turns them into |
| // integers again, this redundant conversion should be removed. |
| // |
| // HandleCommand is only called in response to a Mojo message sent from frames |
| // that have a committed interstitial. This ensures that the current target |
| // frame is present and can process the corresponding command received. |
| SecurityInterstitialPage* blocking_page = |
| GetBlockingPageForCurrentTargetFrame(); |
| // TODO(crbug.com/376688788): Remove this check once the statement above is |
| // guaranteed. |
| if (blocking_page) { |
| blocking_page->CommandReceived(base::NumberToString(cmd)); |
| } |
| } |
| |
| void SecurityInterstitialTabHelper::DontProceed() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_DONT_PROCEED); |
| } |
| |
| void SecurityInterstitialTabHelper::Proceed() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_PROCEED); |
| } |
| |
| void SecurityInterstitialTabHelper::ShowMoreSection() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_SHOW_MORE_SECTION); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenHelpCenter() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_HELP_CENTER); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenDiagnostic() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_OPEN_DIAGNOSTIC); |
| } |
| |
| void SecurityInterstitialTabHelper::Reload() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_RELOAD); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenDateSettings() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_DATE_SETTINGS); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenLogin() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_OPEN_LOGIN); |
| } |
| |
| void SecurityInterstitialTabHelper::DoReport() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_DO_REPORT); |
| } |
| |
| void SecurityInterstitialTabHelper::DontReport() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_DONT_REPORT); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenReportingPrivacy() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_REPORTING_PRIVACY); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenWhitepaper() { |
| HandleCommand( |
| security_interstitials::SecurityInterstitialCommand::CMD_OPEN_WHITEPAPER); |
| } |
| |
| void SecurityInterstitialTabHelper::ReportPhishingError() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_REPORT_PHISHING_ERROR); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenEnhancedProtectionSettings() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_ENHANCED_PROTECTION_SETTINGS); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| void SecurityInterstitialTabHelper::OpenAndroidAdvancedProtectionSettings() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_ANDROID_ADVANCED_PROTECTION_SETTINGS); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| void SecurityInterstitialTabHelper::OpenHelpCenterInNewTab() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_HELP_CENTER_IN_NEW_TAB); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenDiagnosticInNewTab() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_DIAGNOSTIC_IN_NEW_TAB); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenReportingPrivacyInNewTab() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_REPORTING_PRIVACY_IN_NEW_TAB); |
| } |
| |
| void SecurityInterstitialTabHelper::OpenWhitepaperInNewTab() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_OPEN_WHITEPAPER_IN_NEW_TAB); |
| } |
| |
| void SecurityInterstitialTabHelper::ReportPhishingErrorInNewTab() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_REPORT_PHISHING_ERROR_IN_NEW_TAB); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| void SecurityInterstitialTabHelper::ShowCertificateViewer() { |
| HandleCommand(security_interstitials::SecurityInterstitialCommand:: |
| CMD_SHOW_CERTIFICATE_VIEWER); |
| } |
| #endif |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(SecurityInterstitialTabHelper); |
| |
| } // namespace security_interstitials |