| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h" |
| |
| #include <algorithm> |
| |
| #include "ash/shell.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/apps/app_service/app_service_test.h" |
| #include "chrome/browser/ash/policy/dlp/dlp_files_controller_ash.h" |
| #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h" |
| #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h" |
| #include "chrome/browser/chromeos/policy/dlp/test/mock_dlp_rules_manager.h" |
| #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sharesheet/sharesheet_metrics.h" |
| #include "chrome/browser/sharesheet/sharesheet_service.h" |
| #include "chrome/browser/sharesheet/sharesheet_service_factory.h" |
| #include "chrome/browser/sharesheet/sharesheet_test_util.h" |
| #include "chrome/browser/sharesheet/sharesheet_types.h" |
| #include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h" |
| #include "chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h" |
| #include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/components/sharesheet/constants.h" |
| #include "components/services/app_service/public/cpp/intent_test_util.h" |
| #include "components/services/app_service/public/cpp/intent_util.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/window.h" |
| #include "ui/views/bubble/bubble_dialog_delegate_view.h" |
| |
| namespace ash { |
| namespace sharesheet { |
| |
| class SharesheetBubbleViewBrowserTest : public InProcessBrowserTest { |
| public: |
| void ShowUi() { |
| views::Widget::Widgets old_widgets; |
| for (aura::Window* root_window : Shell::GetAllRootWindows()) { |
| old_widgets.merge(views::Widget::GetAllChildWidgets(root_window)); |
| } |
| |
| ::sharesheet::SharesheetService* const sharesheet_service = |
| ::sharesheet::SharesheetServiceFactory::GetForProfile( |
| browser()->profile()); |
| |
| auto intent = apps_util::MakeShareIntent("text", ""); |
| intent->action = apps_util::kIntentActionSend; |
| sharesheet_service->ShowBubble( |
| browser()->tab_strip_model()->GetActiveWebContents(), std::move(intent), |
| ::sharesheet::LaunchSource::kUnknown, base::DoNothing(), |
| base::DoNothing()); |
| |
| views::Widget::Widgets new_widgets; |
| for (aura::Window* root_window : Shell::GetAllRootWindows()) { |
| new_widgets.merge(views::Widget::GetAllChildWidgets(root_window)); |
| } |
| |
| views::Widget::Widgets added_widgets; |
| std::set_difference(new_widgets.begin(), new_widgets.end(), |
| old_widgets.begin(), old_widgets.end(), |
| std::inserter(added_widgets, added_widgets.begin())); |
| ASSERT_EQ(added_widgets.size(), 1u); |
| sharesheet_widget_ = *added_widgets.begin(); |
| ASSERT_EQ(sharesheet_widget_->GetName(), "SharesheetBubbleView"); |
| } |
| |
| bool VerifyUi() { |
| if (sharesheet_widget_) { |
| return sharesheet_widget_->IsVisible(); |
| } |
| return false; |
| } |
| |
| void DismissUi() { |
| ASSERT_TRUE(sharesheet_widget_); |
| sharesheet_widget_->Close(); |
| ASSERT_FALSE(sharesheet_widget_->IsVisible()); |
| } |
| |
| protected: |
| raw_ptr<views::Widget, DanglingUntriaged> sharesheet_widget_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SharesheetBubbleViewBrowserTest, InvokeUi_Default) { |
| ShowUi(); |
| ASSERT_TRUE(VerifyUi()); |
| DismissUi(); |
| } |
| |
| class SharesheetBubbleViewPolicyBrowserTest |
| : public SharesheetBubbleViewBrowserTest { |
| public: |
| class MockFilesController : public policy::DlpFilesControllerAsh { |
| public: |
| explicit MockFilesController(const policy::DlpRulesManager& rules_manager, |
| Profile* profile) |
| : DlpFilesControllerAsh(rules_manager, profile) {} |
| ~MockFilesController() override = default; |
| |
| MOCK_METHOD(bool, |
| IsLaunchBlocked, |
| (const apps::AppUpdate&, const apps::IntentPtr&), |
| (override)); |
| }; |
| |
| void TearDownOnMainThread() override { |
| // Make sure the rules manager does not return a freed files controller. |
| ON_CALL(*rules_manager_, GetDlpFilesController) |
| .WillByDefault(testing::Return(nullptr)); |
| |
| // The files controller must be destroyed before the profile since it's |
| // holding a pointer to it. |
| mock_files_controller_.reset(); |
| |
| SharesheetBubbleViewBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void SetupRulesManager(bool is_dlp_blocked) { |
| policy::DlpRulesManagerFactory::GetInstance()->SetTestingFactory( |
| browser()->profile(), |
| base::BindRepeating( |
| &SharesheetBubbleViewPolicyBrowserTest::SetDlpRulesManager, |
| base::Unretained(this))); |
| ASSERT_TRUE(policy::DlpRulesManagerFactory::GetForPrimaryProfile()); |
| |
| ON_CALL(*rules_manager_, IsFilesPolicyEnabled) |
| .WillByDefault(testing::Return(true)); |
| |
| EXPECT_CALL(*mock_files_controller_.get(), IsLaunchBlocked) |
| .WillOnce(testing::Return(is_dlp_blocked)); |
| } |
| |
| void SetupAppService() { |
| app_service_test_.SetUp(browser()->profile()); |
| |
| AddAppServiceAppsForTesting("arcAppId", apps::AppType::kArc, "text/plain", |
| "https://example.com"); |
| } |
| |
| void AddAppServiceAppsForTesting(std::string app_id, |
| apps::AppType app_type, |
| std::string mime_type, |
| std::optional<std::string> publisher_id) { |
| apps::AppServiceProxy* app_service_proxy = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()); |
| |
| std::vector<apps::AppPtr> fake_apps; |
| apps::AppPtr fake_app = |
| std::make_unique<apps::App>(apps::AppType::kArc, app_id); |
| fake_app->name = "xyz"; |
| fake_app->show_in_management = true; |
| fake_app->readiness = apps::Readiness::kReady; |
| if (publisher_id.has_value()) { |
| fake_app->publisher_id = publisher_id.value(); |
| } |
| std::vector<apps::PermissionPtr> fake_permissions; |
| fake_app->permissions = std::move(fake_permissions); |
| fake_app->handles_intents = true; |
| apps::IntentFilterPtr filter = |
| apps_util::MakeIntentFilterForMimeType(mime_type); |
| fake_app->intent_filters.emplace(); |
| fake_app->intent_filters->push_back(std::move(filter)); |
| |
| fake_apps.push_back(std::move(fake_app)); |
| |
| app_service_proxy->OnApps(std::move(fake_apps), app_type, |
| /*should_notify_initialized=*/false); |
| } |
| |
| bool VerifyDlp(bool is_dlp_blocked) { |
| if (!sharesheet_widget_) { |
| return false; |
| } |
| SharesheetBubbleView* sharesheet_bubble_view_ = |
| static_cast<SharesheetBubbleView*>( |
| sharesheet_widget_->GetContentsView()); |
| views::View* targets = sharesheet_bubble_view_->GetViewByID( |
| SharesheetViewID::TARGETS_DEFAULT_VIEW_ID); |
| SharesheetTargetButton* button = static_cast<SharesheetTargetButton*>( |
| targets->children()[targets->children().size() - 1]); |
| return is_dlp_blocked == |
| (button->GetState() == SharesheetTargetButton::STATE_DISABLED); |
| } |
| |
| MockFilesController* mock_files_controller() { |
| return mock_files_controller_.get(); |
| } |
| |
| private: |
| std::unique_ptr<KeyedService> SetDlpRulesManager( |
| content::BrowserContext* context) { |
| auto dlp_rules_manager = |
| std::make_unique<testing::NiceMock<policy::MockDlpRulesManager>>( |
| Profile::FromBrowserContext(context)); |
| rules_manager_ = dlp_rules_manager.get(); |
| |
| mock_files_controller_ = std::make_unique<MockFilesController>( |
| *rules_manager_, Profile::FromBrowserContext(context)); |
| ON_CALL(*rules_manager_, GetDlpFilesController) |
| .WillByDefault(testing::Return(mock_files_controller_.get())); |
| |
| return dlp_rules_manager; |
| } |
| |
| apps::AppServiceTest app_service_test_; |
| raw_ptr<policy::MockDlpRulesManager, DanglingUntriaged> rules_manager_ = |
| nullptr; |
| std::unique_ptr<MockFilesController> mock_files_controller_ = nullptr; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SharesheetBubbleViewPolicyBrowserTest, |
| InvokeUi_DlpAllowed) { |
| SetupRulesManager(/*is_dlp_blocked*/ false); |
| SetupAppService(); |
| ShowUi(); |
| ASSERT_TRUE(VerifyDlp(/*is_dlp_blocked*/ false)); |
| DismissUi(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharesheetBubbleViewPolicyBrowserTest, |
| InvokeUi_DlpBlocked) { |
| SetupRulesManager(/*is_dlp_blocked*/ true); |
| SetupAppService(); |
| ShowUi(); |
| ASSERT_TRUE(VerifyDlp(/*is_dlp_blocked*/ true)); |
| DismissUi(); |
| } |
| |
| class SharesheetBubbleViewNearbyShareBrowserTest : public InProcessBrowserTest { |
| public: |
| SharesheetBubbleViewNearbyShareBrowserTest() = default; |
| |
| ~SharesheetBubbleViewNearbyShareBrowserTest() override = default; |
| |
| SharesheetBubbleView* sharesheet_bubble_view() { |
| return sharesheet_bubble_view_; |
| } |
| |
| void ShowNearbyShareBubble() { |
| gfx::NativeWindow parent_window = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetTopLevelNativeWindow(); |
| ::sharesheet::SharesheetService* const sharesheet_service = |
| ::sharesheet::SharesheetServiceFactory::GetForProfile( |
| browser()->profile()); |
| sharesheet_service->ShowNearbyShareBubbleForArc( |
| parent_window, ::sharesheet::CreateValidTextIntent(), |
| ::sharesheet::LaunchSource::kArcNearbyShare, |
| /*delivered_callback=*/base::DoNothing(), |
| /*close_callback=*/base::DoNothing(), |
| /*cleanup_callback=*/base::DoNothing()); |
| bubble_delegate_ = static_cast<SharesheetBubbleViewDelegate*>( |
| sharesheet_service->GetUiDelegateForTesting(parent_window)); |
| EXPECT_NE(bubble_delegate_, nullptr); |
| sharesheet_bubble_view_ = bubble_delegate_->GetBubbleViewForTesting(); |
| EXPECT_NE(sharesheet_bubble_view_, nullptr); |
| EXPECT_EQ(sharesheet_bubble_view_->GetID(), SHARESHEET_BUBBLE_VIEW_ID); |
| |
| EXPECT_TRUE(bubble_delegate_->IsBubbleVisible()); |
| auto* sharesheet_widget = sharesheet_bubble_view_->GetWidget(); |
| EXPECT_EQ(sharesheet_widget->GetName(), "SharesheetBubbleView"); |
| EXPECT_TRUE(sharesheet_widget->IsVisible()); |
| } |
| |
| void CloseBubble() { |
| auto* bubble_delegate = bubble_delegate_.get(); |
| // |bubble_delegate_| will be deleted during CloseBubble. |
| bubble_delegate_ = nullptr; |
| bubble_delegate->CloseBubble(::sharesheet::SharesheetResult::kCancel); |
| // |sharesheet_bubble_view_| wlil be deleted asynchronously. |
| sharesheet_bubble_view_ = nullptr; |
| } |
| |
| private: |
| raw_ptr<SharesheetBubbleViewDelegate> bubble_delegate_; |
| raw_ptr<SharesheetBubbleView> sharesheet_bubble_view_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SharesheetBubbleViewNearbyShareBrowserTest, |
| ShowNearbyShareBubbleForArc) { |
| base::HistogramTester histograms; |
| |
| ShowNearbyShareBubble(); |
| |
| histograms.ExpectBucketCount( |
| ::sharesheet::kSharesheetLaunchSourceResultHistogram, |
| ::sharesheet::LaunchSource::kArcNearbyShare, 1); |
| |
| views::View* share_action_view = sharesheet_bubble_view()->GetViewByID( |
| SharesheetViewID::SHARE_ACTION_VIEW_ID); |
| ASSERT_TRUE(share_action_view->GetVisible()); |
| |
| CloseBubble(); |
| } |
| |
| } // namespace sharesheet |
| } // namespace ash |