| // Copyright 2019 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/cloud_content_scanning/deep_scanning_browsertest_base.h" |
| #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_views.h" |
| #include "chrome/browser/safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.h" |
| #include "chrome/browser/ui/browser.h" |
| |
| namespace safe_browsing { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kNoDelay = base::TimeDelta::FromSeconds(0); |
| constexpr base::TimeDelta kSmallDelay = base::TimeDelta::FromMilliseconds(300); |
| constexpr base::TimeDelta kNormalDelay = base::TimeDelta::FromMilliseconds(500); |
| |
| enum class ScanType { ONLY_DLP, ONLY_MALWARE, DLP_AND_MALWARE }; |
| |
| // Tests the behavior of the dialog in the following ways: |
| // - It shows the appropriate buttons depending on it's state. |
| // - It transitions from states in the correct order. |
| // - It respects time constraints (minimum shown time, initial delay, timeout) |
| // - It is always destroyed, therefore |quit_closure_| is called in the dtor |
| // observer. |
| class DeepScanningDialogViewsBehaviorBrowserTest |
| : public DeepScanningBrowserTestBase, |
| public DeepScanningDialogViews::TestObserver, |
| public testing::WithParamInterface< |
| std::tuple<ScanType, bool, bool, base::TimeDelta>> { |
| public: |
| DeepScanningDialogViewsBehaviorBrowserTest() { |
| DeepScanningDialogViews::SetObserverForTesting(this); |
| |
| bool dlp_result = !dlp_enabled() || dlp_success(); |
| bool malware_result = !malware_enabled() || malware_success(); |
| expected_scan_result_ = dlp_result && malware_result; |
| } |
| |
| void ConstructorCalled(DeepScanningDialogViews* views) override { |
| ctor_called_timestamp_ = base::TimeTicks::Now(); |
| dialog_ = views; |
| |
| // The scan should be pending when constructed. |
| EXPECT_TRUE(dialog_->is_pending()); |
| |
| // The dialog should only be constructed once. |
| EXPECT_FALSE(ctor_called_); |
| ctor_called_ = true; |
| } |
| |
| void ViewsFirstShown(DeepScanningDialogViews* views, |
| base::TimeTicks timestamp) override { |
| DCHECK_EQ(views, dialog_); |
| first_shown_timestamp_ = timestamp; |
| |
| // The dialog should only be shown after an initial delay. |
| base::TimeDelta delay = first_shown_timestamp_ - ctor_called_timestamp_; |
| EXPECT_GE(delay, DeepScanningDialogViews::GetInitialUIDelay()); |
| |
| // The dialog can only be first shown in the pending or failure case. |
| EXPECT_TRUE(dialog_->is_pending() || dialog_->is_failure()); |
| |
| // If the failure dialog was shown immediately, ensure that was expected and |
| // set |pending_shown_| for future assertions. |
| if (dialog_->is_failure()) { |
| EXPECT_FALSE(expected_scan_result_); |
| pending_shown_ = false; |
| } else { |
| pending_shown_ = true; |
| } |
| |
| // The dialog's buttons should be Cancel in the pending and fail case. |
| EXPECT_EQ(dialog_->GetDialogButtons(), ui::DIALOG_BUTTON_CANCEL); |
| |
| // The dialog should only be shown once some time after being constructed. |
| EXPECT_TRUE(ctor_called_); |
| EXPECT_FALSE(views_first_shown_); |
| views_first_shown_ = true; |
| } |
| |
| void DialogUpdated(DeepScanningDialogViews* views, bool result) override { |
| DCHECK_EQ(views, dialog_); |
| dialog_updated_timestamp_ = base::TimeTicks::Now(); |
| |
| // The dialog should not be updated if the failure was shown immediately. |
| EXPECT_TRUE(pending_shown_); |
| |
| // The dialog should only be updated after an initial delay. |
| base::TimeDelta delay = dialog_updated_timestamp_ - first_shown_timestamp_; |
| EXPECT_GE(delay, DeepScanningDialogViews::GetMinimumPendingDialogTime()); |
| |
| // The dialog can only be updated to the success or failure case. |
| EXPECT_TRUE(dialog_->is_result()); |
| EXPECT_EQ(dialog_->is_success(), result); |
| EXPECT_EQ(dialog_->is_success(), expected_scan_result_); |
| |
| // The dialog's buttons should be Cancel in the fail case and nothing in the |
| // success case. |
| ui::DialogButton expected_buttons = |
| result ? ui::DIALOG_BUTTON_NONE : ui::DIALOG_BUTTON_CANCEL; |
| EXPECT_EQ(expected_buttons, dialog_->GetDialogButtons()); |
| |
| // The dialog should only be updated once some time after being shown. |
| EXPECT_TRUE(views_first_shown_); |
| EXPECT_FALSE(dialog_updated_); |
| dialog_updated_ = true; |
| } |
| |
| void DestructorCalled(DeepScanningDialogViews* views) override { |
| dtor_called_timestamp_ = base::TimeTicks::Now(); |
| |
| EXPECT_TRUE(views); |
| EXPECT_EQ(dialog_, views); |
| EXPECT_EQ(dialog_->is_success(), expected_scan_result_); |
| |
| if (views_first_shown_) { |
| // Ensure the dialog update only occurred if the pending state was shown. |
| EXPECT_EQ(pending_shown_, dialog_updated_); |
| |
| // Ensure the success UI timed out properly. |
| EXPECT_TRUE(dialog_->is_result()); |
| if (dialog_->is_success()) { |
| // The success dialog should stay open for some time. |
| base::TimeDelta delay = |
| dtor_called_timestamp_ - dialog_updated_timestamp_; |
| EXPECT_GE(delay, DeepScanningDialogViews::GetSuccessDialogTimeout()); |
| |
| EXPECT_EQ(ui::DIALOG_BUTTON_NONE, dialog_->GetDialogButtons()); |
| } else { |
| EXPECT_EQ(ui::DIALOG_BUTTON_CANCEL, dialog_->GetDialogButtons()); |
| } |
| } else { |
| // Ensure the dialog update didn't occur if no dialog was shown. |
| EXPECT_FALSE(dialog_updated_); |
| } |
| EXPECT_TRUE(ctor_called_); |
| |
| // The test is over once the views are destroyed. |
| CallQuitClosure(); |
| } |
| |
| bool dlp_enabled() const { |
| return std::get<0>(GetParam()) != ScanType::ONLY_MALWARE; |
| } |
| |
| bool malware_enabled() const { |
| return std::get<0>(GetParam()) != ScanType::ONLY_DLP; |
| } |
| |
| bool dlp_success() const { return std::get<1>(GetParam()); } |
| |
| bool malware_success() const { return std::get<2>(GetParam()); } |
| |
| base::TimeDelta response_delay() const { return std::get<3>(GetParam()); } |
| |
| private: |
| DeepScanningDialogViews* dialog_; |
| |
| base::TimeTicks ctor_called_timestamp_; |
| base::TimeTicks first_shown_timestamp_; |
| base::TimeTicks dialog_updated_timestamp_; |
| base::TimeTicks dtor_called_timestamp_; |
| |
| bool pending_shown_ = false; |
| bool ctor_called_ = false; |
| bool views_first_shown_ = false; |
| bool dialog_updated_ = false; |
| |
| bool expected_scan_result_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_P(DeepScanningDialogViewsBehaviorBrowserTest, Test) { |
| // The test is wrong if neither DLP or Malware is enabled. This would imply a |
| // Deep Scanning call site called ShowForWebContents without first checking |
| // IsEnabled returns true. |
| EXPECT_TRUE(dlp_enabled() || malware_enabled()); |
| |
| // Setup policies to enable deep scanning, its UI and the responses to be |
| // simulated. |
| base::Optional<bool> dlp = base::nullopt; |
| base::Optional<bool> malware = base::nullopt; |
| if (dlp_enabled()) { |
| dlp = dlp_success(); |
| SetDlpPolicy(CHECK_UPLOADS_AND_DOWNLOADS); |
| } |
| if (malware_enabled()) { |
| malware = malware_success(); |
| SetMalwarePolicy(SEND_UPLOADS_AND_DOWNLOADS); |
| } |
| SetStatusCallbackResponse( |
| SimpleDeepScanningClientResponseForTesting(dlp, malware)); |
| |
| // Always set this policy so the UI is shown. |
| SetWaitPolicy(DELAY_UPLOADS); |
| |
| // Set up delegate test values. |
| FakeDeepScanningDialogDelegate::SetResponseDelay(response_delay()); |
| SetUpDelegate(); |
| |
| bool called = false; |
| base::RunLoop run_loop; |
| SetQuitClosure(run_loop.QuitClosure()); |
| |
| DeepScanningDialogDelegate::Data data; |
| data.do_dlp_scan = dlp_enabled(); |
| data.do_malware_scan = malware_enabled(); |
| data.paths.emplace_back(FILE_PATH_LITERAL("/tmp/foo.doc")); |
| |
| DeepScanningDialogDelegate::ShowForWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents(), std::move(data), |
| base::BindOnce( |
| [](bool* called, const DeepScanningDialogDelegate::Data& data, |
| const DeepScanningDialogDelegate::Result& result) { |
| *called = true; |
| }, |
| &called), |
| DeepScanAccessPoint::UPLOAD); |
| run_loop.Run(); |
| EXPECT_TRUE(called); |
| } |
| |
| // The scan type controls if DLP, malware or both are enabled via policies. The |
| // dialog currently behaves identically in all 3 cases, so this parameter |
| // ensures this assumption is not broken by new code. |
| // |
| // The DLP/Malware success parameters determine how the response is populated, |
| // and therefore what the dialog should show. |
| // |
| // The three different delays test three cases: |
| // kNoDelay: The response is as fast as possible, and therefore the pending |
| // UI is not shown (kNoDelay < GetInitialUIDelay). |
| // kSmallDelay: The response is not fast enough to prevent the pending UI from |
| // showing, but fast enough that it hasn't been show long enough |
| // (GetInitialDelay < kSmallDelay < GetMinimumPendingDialogTime). |
| // kNormalDelay: The response is slow enough that the pending UI is shown for |
| // more than its minimum duration (GetMinimumPendingDialogTime < |
| // kNormalDelay). |
| INSTANTIATE_TEST_SUITE_P( |
| DeepScanningDialogViewsBehaviorBrowserTest, |
| DeepScanningDialogViewsBehaviorBrowserTest, |
| testing::Combine( |
| /*scan_type*/ testing::Values(ScanType::ONLY_DLP, |
| ScanType::ONLY_MALWARE, |
| ScanType::DLP_AND_MALWARE), |
| /*dlp_success*/ testing::Bool(), |
| /*malware_success*/ testing::Bool(), |
| /*response_delay*/ |
| testing::Values(kNoDelay, kSmallDelay, kNormalDelay))); |
| |
| } // namespace safe_browsing |