| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/path_service.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/to_string.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/config/chromebox_for_meetings/buildflags.h" // PLATFORM_CFM |
| #include "chrome/browser/apps/platform_apps/app_browsertest_util.h" |
| #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/infobars/content/content_infobar_manager.h" |
| #include "components/infobars/core/infobar.h" |
| #include "components/permissions/permission_request_manager.h" |
| #include "components/policy/core/browser/browser_policy_connector.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/url_formatter/elide_url.h" |
| #include "content/public/browser/audio_service.h" |
| #include "content/public/browser/host_zoom_map.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/mock_captured_surface_controller.h" |
| #include "media/audio/audio_features.h" |
| #include "media/audio/audio_system.h" |
| #include "media/base/media_switches.h" |
| #include "net/base/filename_util.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/page/page_zoom.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gl/gl_switches.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "base/mac/mac_util.h" |
| #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h" |
| #include "chrome/browser/chromeos/policy/dlp/test/dlp_content_manager_test_helper.h" |
| #endif |
| |
| namespace { |
| |
| using ::base::test::FeatureRef; |
| using ::content::BrowserThread; |
| using ::content::CapturedSurfaceController; |
| using ::content::GlobalRenderFrameHostId; |
| using ::content::MockCapturedSurfaceController; |
| using ::content::WebContents; |
| using ::content::WebContentsMediaCaptureId; |
| using ::testing::_; |
| using ::testing::Bool; |
| using ::testing::Combine; |
| using ::testing::HasSubstr; |
| using ::testing::Mock; |
| using ::testing::Values; |
| using TabSharingInfoBarButton = |
| ::TabSharingInfoBarDelegate::TabSharingInfoBarButton; |
| |
| using CapturedSurfaceControllerFactoryCallback = |
| ::base::RepeatingCallback<std::unique_ptr<MockCapturedSurfaceController>( |
| GlobalRenderFrameHostId, |
| WebContentsMediaCaptureId)>; |
| |
| static const char kMainHtmlPage[] = "/webrtc/webrtc_getdisplaymedia_test.html"; |
| static const char kMainHtmlFileName[] = "webrtc_getdisplaymedia_test.html"; |
| static const char kSameOriginRenamedTitle[] = "Renamed Same Origin Tab"; |
| static const char kMainHtmlTitle[] = "WebRTC Automated Test"; |
| // The captured tab is identified by its title. |
| static const char kCapturedTabTitle[] = "totally-unique-captured-page-title"; |
| static const char kCapturedPageMain[] = "/webrtc/captured_page_main.html"; |
| static const std::u16string kShareThisTabInsteadMessage = |
| u"Share this tab instead"; |
| |
| constexpr TabSharingInfoBarButton kCscIndicator = |
| TabSharingInfoBarButton::kCapturedSurfaceControlIndicator; |
| |
| enum class DisplaySurfaceType { kTab, kWindow, kScreen }; |
| |
| enum class GetDisplayMediaVariant : int { |
| kStandard = 0, |
| kPreferCurrentTab = 1 |
| }; |
| |
| struct TestConfigForPicker { |
| TestConfigForPicker(bool should_prefer_current_tab, |
| bool accept_this_tab_capture) |
| : should_prefer_current_tab(should_prefer_current_tab), |
| accept_this_tab_capture(accept_this_tab_capture) {} |
| |
| explicit TestConfigForPicker(std::tuple<bool, bool> input_tuple) |
| : TestConfigForPicker(std::get<0>(input_tuple), |
| std::get<1>(input_tuple)) {} |
| |
| // If true, specify {preferCurrentTab: true}. |
| // Otherwise, either don't specify it, or set it to false. |
| bool should_prefer_current_tab; |
| |
| // |accept_this_tab_capture| is only applicable if |
| // |should_prefer_current_tab| is set to true. Then, setting |
| // |accept_this_tab_capture| to true accepts the current tab, and |
| // |accept_this_tab_capture| set to false implies dismissing the media picker. |
| bool accept_this_tab_capture; |
| }; |
| |
| struct TestConfigForFakeUI { |
| bool should_prefer_current_tab; |
| const char* display_surface; |
| }; |
| |
| struct TestConfigForMediaResolution { |
| int constraint_width; |
| int constraint_height; |
| }; |
| |
| struct TestConfigForRestrictOwnAudio { |
| TestConfigForRestrictOwnAudio(bool restrict_own_audio, |
| bool suppress_local_audio_playback) |
| : restrict_own_audio(restrict_own_audio), |
| suppress_local_audio_playback(suppress_local_audio_playback) {} |
| |
| explicit TestConfigForRestrictOwnAudio(std::tuple<bool, bool> input_tuple) |
| : TestConfigForRestrictOwnAudio(std::get<0>(input_tuple), |
| std::get<1>(input_tuple)) {} |
| bool restrict_own_audio; |
| bool suppress_local_audio_playback; |
| }; |
| |
| constexpr char kAppWindowTitle[] = "AppWindow Display Capture Test"; |
| |
| constexpr char kEmbeddedTestServerOrigin[] = "http://127.0.0.1"; |
| constexpr char kOtherOrigin[] = "https://other-origin.com"; |
| |
| std::string DisplaySurfaceTypeAsString( |
| DisplaySurfaceType display_surface_type) { |
| switch (display_surface_type) { |
| case DisplaySurfaceType::kTab: |
| return "browser"; |
| case DisplaySurfaceType::kWindow: |
| return "window"; |
| case DisplaySurfaceType::kScreen: |
| return "screen"; |
| } |
| NOTREACHED(); |
| } |
| |
| void RunGetDisplayMedia(content::WebContents* tab, |
| const std::string& constraints, |
| bool is_fake_ui, |
| bool expect_success, |
| bool is_tab_capture, |
| const std::string& expected_error = "", |
| bool with_user_gesture = true) { |
| DCHECK(!expect_success || expected_error.empty()); |
| |
| const content::ToRenderFrameHost& adapter = tab->GetPrimaryMainFrame(); |
| const std::string script = base::StringPrintf( |
| "runGetDisplayMedia(%s, \"top-level-document\", \"%s\");", |
| constraints.c_str(), expected_error.c_str()); |
| std::string result = |
| content::EvalJs(adapter, script, |
| with_user_gesture |
| ? content::EXECUTE_SCRIPT_DEFAULT_OPTIONS |
| : content::EXECUTE_SCRIPT_NO_USER_GESTURE) |
| .ExtractString(); |
| |
| #if BUILDFLAG(IS_MAC) |
| if (!is_fake_ui && !is_tab_capture && |
| system_media_permissions::CheckSystemScreenCapturePermission() != |
| system_permission_settings::SystemPermission::kAllowed) { |
| expect_success = false; |
| } |
| #endif |
| |
| EXPECT_EQ(result, expect_success ? "capture-success" |
| : expected_error.empty() ? "capture-failure" |
| : "expected-error"); |
| } |
| |
| void RunGetUserMedia(content::WebContents* tab, |
| const std::string& constraints) { |
| const std::string script = |
| base::StrCat({"runGetUserMedia(", constraints, ");"}); |
| std::string result = content::EvalJs(tab->GetPrimaryMainFrame(), script, |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE) |
| .ExtractString(); |
| EXPECT_EQ(result, "gum-success"); |
| } |
| |
| void StopAllTracks(content::WebContents* tab) { |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "stopAllTracks();"), |
| "stopped"); |
| } |
| |
| void UpdateWebContentsTitle(content::WebContents* contents, |
| const std::u16string& title) { |
| content::NavigationEntry* entry = |
| contents->GetController().GetLastCommittedEntry(); |
| ASSERT_TRUE(entry); |
| contents->UpdateTitleForEntry(entry, title); |
| } |
| |
| GURL GetFileURL(const char* filename) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::FilePath path; |
| base::PathService::Get(chrome::DIR_TEST_DATA, &path); |
| path = path.AppendASCII("webrtc").AppendASCII(filename); |
| CHECK(base::PathExists(path)); |
| return net::FilePathToFileURL(path); |
| } |
| |
| infobars::ContentInfoBarManager* GetInfoBarManager( |
| content::WebContents* web_contents) { |
| return infobars::ContentInfoBarManager::FromWebContents(web_contents); |
| } |
| |
| TabSharingInfoBarDelegate* GetDelegate(content::WebContents* web_contents, |
| size_t infobar_index = 0) { |
| return static_cast<TabSharingInfoBarDelegate*>( |
| GetInfoBarManager(web_contents)->infobars()[infobar_index]->delegate()); |
| } |
| |
| bool HasCscIndicator(content::WebContents* web_contents) { |
| return GetDelegate(web_contents)->GetButtons() & kCscIndicator; |
| } |
| |
| bool HasShareThisTabInsteadButton(content::WebContents* web_contents) { |
| return GetDelegate(web_contents)->GetButtons() & |
| TabSharingInfoBarButton::kShareThisTabInstead; |
| } |
| |
| std::u16string GetShareThisTabInsteadButtonLabel( |
| content::WebContents* web_contents) { |
| DCHECK(HasShareThisTabInsteadButton(web_contents)); // Test error otherwise. |
| return GetDelegate(web_contents) |
| ->GetButtonLabel(TabSharingInfoBarButton::kShareThisTabInstead); |
| } |
| |
| void AdjustCommandLineForZeroCopyCapture(base::CommandLine* command_line) { |
| CHECK(command_line); |
| |
| // MSan and GL do not get along so avoid using the GPU with MSan. |
| // TODO(crbug.com/40260482): Remove this after fixing feature |
| // detection in 0c tab capture path as it'll no longer be needed. |
| #if !BUILDFLAG(IS_CHROMEOS) && !defined(MEMORY_SANITIZER) |
| command_line->AppendSwitch(switches::kUseGpuInTests); |
| #endif |
| } |
| |
| // The concept of "zoom level" is overloaded. For clarity, when we mean the |
| // "factor times 100," we'll just name it "zoom level percentage," at least |
| // in tests. |
| int GetZoomLevelPercentage(WebContents* wc) { |
| return std::round(100 * blink::ZoomLevelToZoomFactor( |
| content::HostZoomMap::GetZoomLevel(wc))); |
| } |
| |
| void SetZoomFactor(WebContents* wc, double zoom_factor) { |
| content::HostZoomMap* const host_zoom_map = |
| content::HostZoomMap::GetForWebContents(wc); |
| CHECK(host_zoom_map); |
| |
| host_zoom_map->SetTemporaryZoomLevel( |
| wc->GetPrimaryMainFrame()->GetGlobalId(), |
| blink::ZoomFactorToZoomLevel(zoom_factor)); |
| |
| if (!blink::ZoomValuesEqual(GetZoomLevelPercentage(wc), 100 * zoom_factor)) { |
| FAIL(); // Abort test, not just the helper method. |
| } |
| } |
| |
| } // namespace |
| |
| // Base class for top level tests for getDisplayMedia(). |
| class WebRtcScreenCaptureBrowserTest : public WebRtcTestBase { |
| public: |
| ~WebRtcScreenCaptureBrowserTest() override = default; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| DetectErrorsInJavaScript(); |
| } |
| |
| virtual bool PreferCurrentTab() const = 0; |
| |
| std::string GetConstraints(bool video, |
| bool audio, |
| bool prefer_current_tab) const { |
| return base::StringPrintf("{video: %s, audio: %s, preferCurrentTab: %s}", |
| base::ToString(video), base::ToString(audio), |
| base::ToString(prefer_current_tab)); |
| } |
| |
| std::string GetConstraints(bool video, |
| bool audio, |
| GetDisplayMediaVariant variant) const { |
| return GetConstraints(video, audio, |
| variant == GetDisplayMediaVariant::kPreferCurrentTab); |
| } |
| |
| std::string GetConstraints(bool video, bool audio) const { |
| return GetConstraints(video, audio, PreferCurrentTab()); |
| } |
| }; |
| |
| // Top level test for getDisplayMedia(). |
| // Pops picker UI and shares by default. |
| class WebRtcScreenCaptureBrowserTestWithPicker |
| : public WebRtcScreenCaptureBrowserTest, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| WebRtcScreenCaptureBrowserTestWithPicker() : test_config_(GetParam()) {} |
| |
| void SetUpOnMainThread() override { |
| WebRtcScreenCaptureBrowserTest::SetUpOnMainThread(); |
| #if BUILDFLAG(PLATFORM_CFM) |
| if (test_config_.should_prefer_current_tab && |
| !test_config_.accept_this_tab_capture) { |
| GTEST_SKIP(); // CFMs always automatically accept current-tab captures. |
| } |
| #endif |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| if (test_config_.should_prefer_current_tab) { |
| command_line->AppendSwitch(test_config_.accept_this_tab_capture |
| ? switches::kThisTabCaptureAutoAccept |
| : switches::kThisTabCaptureAutoReject); |
| } else { |
| #if BUILDFLAG(IS_CHROMEOS) |
| command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource, |
| "Display"); |
| #else |
| command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource, |
| "Entire screen"); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| } |
| |
| bool PreferCurrentTab() const override { |
| return test_config_.should_prefer_current_tab; |
| } |
| |
| const TestConfigForPicker test_config_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| WebRtcScreenCaptureBrowserTestWithPicker, |
| Combine( |
| /*should_prefer_current_tab=*/Bool(), |
| /*accept_this_tab_capture=*/Bool())); |
| |
| // TODO(crbug.com/40744542): Real desktop capture is flaky on below platforms. |
| // TODO(crbug.com/41493366): enable this flaky test. |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| #define MAYBE_ScreenCaptureVideo DISABLED_ScreenCaptureVideo |
| #else |
| #define MAYBE_ScreenCaptureVideo ScreenCaptureVideo |
| #endif // BUILDFLAG(IS_WIN) |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithPicker, |
| MAYBE_ScreenCaptureVideo) { |
| if (!test_config_.should_prefer_current_tab && |
| !test_config_.accept_this_tab_capture) { |
| GTEST_SKIP(); |
| } |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| RunGetDisplayMedia(tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/false), |
| /*is_fake_ui=*/false, |
| /*expect_success=*/test_config_.accept_this_tab_capture, |
| /*is_tab_capture=*/PreferCurrentTab()); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithPicker, |
| ScreenCaptureVideoWithDlp) { |
| if (!test_config_.should_prefer_current_tab && |
| !test_config_.accept_this_tab_capture) { |
| GTEST_SKIP(); |
| } |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| policy::DlpContentManagerTestHelper helper; |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| RunGetDisplayMedia(tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/false), |
| /*is_fake_ui=*/false, |
| /*expect_success=*/test_config_.accept_this_tab_capture, |
| /*is_tab_capture=*/PreferCurrentTab()); |
| |
| if (!test_config_.accept_this_tab_capture) { |
| // This test is not relevant for this parameterized test case because it |
| // does not capture the tab/display surface. |
| return; |
| } |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "waitVideoUnmuted();"), |
| "unmuted"); |
| |
| const policy::DlpContentRestrictionSet kScreenShareRestricted( |
| policy::DlpContentRestriction::kScreenShare, |
| policy::DlpRulesManager::Level::kBlock); |
| |
| helper.ChangeConfidentiality(tab, kScreenShareRestricted); |
| content::WaitForLoadStop(tab); |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "waitVideoMuted();"), |
| "muted"); |
| |
| const policy::DlpContentRestrictionSet kEmptyRestrictionSet; |
| helper.ChangeConfidentiality(tab, kEmptyRestrictionSet); |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "waitVideoUnmuted();"), |
| "unmuted"); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| // TODO(crbug.com/40744542): Real desktop capture is flaky on below platforms. |
| // TODO(crbug.com/41493366): enable this flaky test. |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| #define MAYBE_ScreenCaptureVideoAndAudio DISABLED_ScreenCaptureVideoAndAudio |
| // On linux debug bots, it's flaky as well. |
| #elif BUILDFLAG(IS_LINUX) && !defined(NDEBUG) |
| #define MAYBE_ScreenCaptureVideoAndAudio DISABLED_ScreenCaptureVideoAndAudio |
| // On linux asan bots, it's flaky as well - msan and other rel bot are fine. |
| #elif BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER) |
| #define MAYBE_ScreenCaptureVideoAndAudio DISABLED_ScreenCaptureVideoAndAudio |
| #else |
| #define MAYBE_ScreenCaptureVideoAndAudio ScreenCaptureVideoAndAudio |
| #endif // BUILDFLAG(IS_WIN) |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithPicker, |
| MAYBE_ScreenCaptureVideoAndAudio) { |
| if (!test_config_.should_prefer_current_tab && |
| !test_config_.accept_this_tab_capture) { |
| GTEST_SKIP(); |
| } |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| RunGetDisplayMedia(tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/true), |
| /*is_fake_ui=*/false, |
| /*expect_success=*/test_config_.accept_this_tab_capture, |
| /*is_tab_capture=*/PreferCurrentTab()); |
| } |
| |
| // Top level test for getDisplayMedia(). |
| // Skips picker UI and uses fake device with specified type. |
| class WebRtcScreenCaptureBrowserTestWithFakeUI |
| : public WebRtcScreenCaptureBrowserTest, |
| public testing::WithParamInterface<TestConfigForFakeUI> { |
| public: |
| WebRtcScreenCaptureBrowserTestWithFakeUI() : test_config_(GetParam()) {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitch(switches::kUseFakeUIForMediaStream); |
| command_line->RemoveSwitch(switches::kUseFakeDeviceForMediaStream); |
| command_line->AppendSwitchASCII( |
| switches::kUseFakeDeviceForMediaStream, |
| base::StringPrintf("display-media-type=%s", |
| test_config_.display_surface)); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| } |
| |
| bool PreferCurrentTab() const override { |
| return test_config_.should_prefer_current_tab; |
| } |
| |
| protected: |
| const TestConfigForFakeUI test_config_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithFakeUI, |
| ScreenCaptureVideo) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| RunGetDisplayMedia(tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/false), |
| /*is_fake_ui=*/true, /*expect_success=*/true, |
| /*is_tab_capture=*/PreferCurrentTab()); |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), |
| "getDisplaySurfaceSetting();"), |
| test_config_.display_surface); |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), |
| "getLogicalSurfaceSetting();"), |
| "true"); |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "getCursorSetting();"), |
| "never"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithFakeUI, |
| ScreenCaptureVideoAndAudio) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| RunGetDisplayMedia(tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/true), |
| /*is_fake_ui=*/true, /*expect_success=*/true, |
| /*is_tab_capture=*/PreferCurrentTab()); |
| |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "hasAudioTrack();"), |
| "true"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithFakeUI, |
| ScreenCaptureWithConstraints) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| const int kMaxWidth = 200; |
| const int kMaxFrameRate = 6; |
| const std::string constraints = base::StringPrintf( |
| "{video: {width: {max: %d}, frameRate: {max: %d}}, " |
| "should_prefer_current_tab: " |
| "%s}", |
| kMaxWidth, kMaxFrameRate, |
| base::ToString(test_config_.should_prefer_current_tab)); |
| RunGetDisplayMedia(tab, constraints, |
| /*is_fake_ui=*/true, /*expect_success=*/true, |
| /*is_tab_capture=*/PreferCurrentTab()); |
| auto result = |
| content::EvalJs(tab->GetPrimaryMainFrame(), "getWidthSetting();") |
| .ExtractString(); |
| int value; |
| EXPECT_TRUE(base::StringToInt(result, &value)); |
| EXPECT_LE(value, kMaxWidth); |
| |
| EXPECT_EQ( |
| content::EvalJs(tab->GetPrimaryMainFrame(), "getFrameRateSetting();"), |
| base::StringPrintf("%d", kMaxFrameRate)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| WebRtcScreenCaptureBrowserTestWithFakeUI, |
| Values(TestConfigForFakeUI{/*should_prefer_current_tab=*/false, |
| /*display_surface=*/"monitor"}, |
| TestConfigForFakeUI{/*should_prefer_current_tab=*/false, |
| /*display_surface=*/"window"}, |
| TestConfigForFakeUI{/*should_prefer_current_tab=*/false, |
| /*display_surface=*/"browser"}, |
| TestConfigForFakeUI{/*should_prefer_current_tab=*/true, |
| /*display_surface=*/"browser"})); |
| |
| class WebRtcScreenCapturePermissionPolicyBrowserTest |
| : public WebRtcScreenCaptureBrowserTest, |
| public testing::WithParamInterface< |
| std::tuple<GetDisplayMediaVariant, bool>> { |
| public: |
| WebRtcScreenCapturePermissionPolicyBrowserTest() |
| : tested_variant_(std::get<0>(GetParam())), |
| allowlisted_by_policy_(std::get<1>(GetParam())) {} |
| |
| ~WebRtcScreenCapturePermissionPolicyBrowserTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kMainHtmlTitle); |
| } |
| |
| bool PreferCurrentTab() const override { |
| return tested_variant_ == GetDisplayMediaVariant::kPreferCurrentTab; |
| } |
| |
| protected: |
| const GetDisplayMediaVariant tested_variant_; |
| const bool allowlisted_by_policy_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| WebRtcScreenCapturePermissionPolicyBrowserTest, |
| Combine(Values(GetDisplayMediaVariant::kStandard, |
| GetDisplayMediaVariant::kPreferCurrentTab), |
| /*allowlisted_by_policy=*/Bool())); |
| |
| // Flaky on Win bots http://crbug.com/1264805 |
| #if BUILDFLAG(IS_WIN) |
| #define MAYBE_ScreenShareFromEmbedded DISABLED_ScreenShareFromEmbedded |
| #else |
| #define MAYBE_ScreenShareFromEmbedded ScreenShareFromEmbedded |
| #endif |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCapturePermissionPolicyBrowserTest, |
| MAYBE_ScreenShareFromEmbedded) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // The use of selfBrowserSurface is in order to simplify the test by |
| // using just one tab. It is orthogonal to the test's purpose. |
| const std::string constraints = base::StringPrintf( |
| "{video: true, selfBrowserSurface: 'include', preferCurrentTab: %s}", |
| base::ToString(PreferCurrentTab())); |
| |
| EXPECT_EQ( |
| content::EvalJs( |
| OpenTestPageInNewTab(kMainHtmlPage)->GetPrimaryMainFrame(), |
| base::StringPrintf( |
| "runGetDisplayMedia(%s, \"%s\");", constraints.c_str(), |
| allowlisted_by_policy_ ? "allowedFrame" : "disallowedFrame")), |
| allowlisted_by_policy_ ? "embedded-capture-success" |
| : "embedded-capture-failure"); |
| } |
| |
| // Test class used to test WebRTC with App Windows. Unfortunately, due to |
| // creating a diamond pattern of inheritance, we can only inherit from one of |
| // the PlatformAppBrowserTest and WebRtcBrowserTestBase (or it's children). |
| // We need a lot more heavy lifting on creating the AppWindow than we would get |
| // from WebRtcBrowserTestBase; so we inherit from PlatformAppBrowserTest to |
| // minimize the code duplication. |
| class WebRtcAppWindowCaptureBrowserTestWithPicker |
| : public extensions::PlatformAppBrowserTest { |
| public: |
| WebRtcAppWindowCaptureBrowserTestWithPicker() = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| PlatformAppBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kAppWindowTitle); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| extensions::PlatformAppBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| |
| // We will restrict all pages to "Tab Capture" only. This should force App |
| // Windows to show up in the tabs list, and thus make it selectable. |
| base::Value::List matchlist; |
| matchlist.Append("*"); |
| browser()->profile()->GetPrefs()->SetList( |
| prefs::kTabCaptureAllowedByOrigins, std::move(matchlist)); |
| } |
| |
| void TearDownOnMainThread() override { |
| extensions::PlatformAppBrowserTest::TearDownOnMainThread(); |
| browser()->profile()->GetPrefs()->SetList( |
| prefs::kTabCaptureAllowedByOrigins, base::Value::List()); |
| } |
| |
| extensions::AppWindow* CreateAppWindowWithTitle(const std::u16string& title) { |
| extensions::AppWindow* app_window = CreateTestAppWindow("{}"); |
| EXPECT_TRUE(app_window); |
| UpdateWebContentsTitle(app_window->web_contents(), title); |
| |
| return app_window; |
| } |
| |
| // This is mostly lifted from WebRtcBrowserTestBase, with the exception that |
| // because we know we're setting the auto-accept switches, we don't need to |
| // set the PermissionsManager auto accept. |
| content::WebContents* OpenTestPageInNewTab(const std::string& test_url) { |
| chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), -1, true); |
| GURL url = embedded_test_server()->GetURL(test_url); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(WebRtcAppWindowCaptureBrowserTestWithPicker, |
| CaptureAppWindow) { |
| extensions::AppWindow* app_window = |
| CreateAppWindowWithTitle(base::UTF8ToUTF16(std::string(kAppWindowTitle))); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| RunGetDisplayMedia(capturing_tab, "{video: true}", /*is_fake_ui=*/false, |
| /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| CloseAppWindow(app_window); |
| } |
| |
| // Base class for running tests with a SameOrigin policy applied. |
| class WebRtcSameOriginPolicyBrowserTest |
| : public WebRtcScreenCaptureBrowserTest { |
| public: |
| ~WebRtcSameOriginPolicyBrowserTest() override = default; |
| |
| bool PreferCurrentTab() const override { return false; } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| WebRtcScreenCaptureBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kSameOriginRenamedTitle); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| WebRtcScreenCaptureBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Restrict all origins to SameOrigin tab capture only. |
| base::Value::List matchlist; |
| matchlist.Append("*"); |
| browser()->profile()->GetPrefs()->SetList( |
| prefs::kSameOriginTabCaptureAllowedByOrigins, std::move(matchlist)); |
| } |
| |
| void TearDownOnMainThread() override { |
| WebRtcScreenCaptureBrowserTest::TearDownOnMainThread(); |
| browser()->profile()->GetPrefs()->SetList( |
| prefs::kSameOriginTabCaptureAllowedByOrigins, base::Value::List()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(WebRtcSameOriginPolicyBrowserTest, |
| TerminateOnNavigationAwayFromSameOrigin) { |
| // Open two pages, one to be captured, and one to do the capturing. Note that |
| // we open the capturing page second so that is focused to allow the |
| // getDisplayMedia request to succeed. |
| content::WebContents* target_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| // Update the target tab to a unique title, so that we can ensure that it is |
| // the one that gets captured via the autoselection. |
| UpdateWebContentsTitle( |
| target_tab, base::UTF8ToUTF16(std::string(kSameOriginRenamedTitle))); |
| RunGetDisplayMedia(capturing_tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/true), |
| /*is_fake_ui=*/false, /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| |
| // Though the target tab should've been focused as a result of starting the |
| // capture, we don't want to take a dependency on that behavior. Ensure that |
| // the target tab is focused, so that we can navigate it easily. If it is |
| // already focused, this will just no-op. |
| int target_index = |
| browser()->tab_strip_model()->GetIndexOfWebContents(target_tab); |
| browser()->tab_strip_model()->ActivateTabAt( |
| target_index, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(target_tab, browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| // We navigate to a FileURL so that the origin will change, which should |
| // trigger the capture to end. |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GetFileURL(kMainHtmlFileName))); |
| |
| // Verify that the video stream has ended. |
| EXPECT_EQ(content::EvalJs(capturing_tab->GetPrimaryMainFrame(), |
| "waitVideoEnded();"), |
| "ended"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebRtcSameOriginPolicyBrowserTest, |
| ContinueCapturingForSameOriginNavigation) { |
| // Open two pages, one to be captured, and one to do the capturing. Note that |
| // we open the capturing page second so that is focused to allow the |
| // getDisplayMedia request to succeed. |
| content::WebContents* target_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| // Update the target tab to a unique title, so that we can ensure that it is |
| // the one that gets captured via the autoselection. |
| UpdateWebContentsTitle( |
| target_tab, base::UTF8ToUTF16(std::string(kSameOriginRenamedTitle))); |
| RunGetDisplayMedia(capturing_tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/true), |
| /*is_fake_ui=*/false, /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| |
| // Though the target tab should've been focused as a result of starting the |
| // capture, we don't want to take a dependency on that behavior. Ensure that |
| // the target tab is focused, so that we can navigate it easily. If it is |
| // already focused, this will just no-op. |
| int target_index = |
| browser()->tab_strip_model()->GetIndexOfWebContents(target_tab); |
| browser()->tab_strip_model()->ActivateTabAt( |
| target_index, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| ASSERT_EQ(target_tab, browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| // We navigate using the test server so that the origin doesn't change. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("/webrtc/captured_page_main.html"))); |
| |
| // Verify that the video hasn't been ended. |
| EXPECT_EQ(content::EvalJs(capturing_tab->GetPrimaryMainFrame(), |
| "video_track.readyState;"), |
| "live"); |
| } |
| |
| class GetDisplayMediaVideoTrackBrowserTest |
| : public WebRtcTestBase, |
| public testing::WithParamInterface<std::tuple<bool, DisplaySurfaceType>> { |
| public: |
| GetDisplayMediaVideoTrackBrowserTest() |
| : region_capture_enabled_(std::get<0>(GetParam())), |
| display_surface_type_(std::get<1>(GetParam())) {} |
| |
| ~GetDisplayMediaVideoTrackBrowserTest() override = default; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| DetectErrorsInJavaScript(); |
| } |
| |
| void SetUpOnMainThread() override { |
| WebRtcTestBase::SetUpOnMainThread(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| // Unlike SetUp(), this is called from the test body. This allows skipping |
| // this test for (platform, test-case) combinations which are not supported. |
| void SetupTest() { |
| // Fire up the page. |
| tab_ = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| // Initiate the capture. |
| ASSERT_EQ("capture-success", |
| content::EvalJs(tab_->GetPrimaryMainFrame(), |
| "runGetDisplayMedia({video: true, audio: true}, " |
| "\"top-level-document\");")); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| WebRtcTestBase::SetUpCommandLine(command_line); |
| |
| std::vector<std::string> enabled_blink_features; |
| std::vector<std::string> disabled_blink_features; |
| |
| if (region_capture_enabled_) { |
| enabled_blink_features.push_back("RegionCapture"); |
| } else { |
| disabled_blink_features.push_back("RegionCapture"); |
| } |
| |
| if (!enabled_blink_features.empty()) { |
| command_line->AppendSwitchASCII( |
| switches::kEnableBlinkFeatures, |
| base::JoinString(enabled_blink_features, ",")); |
| } |
| |
| if (!disabled_blink_features.empty()) { |
| command_line->AppendSwitchASCII( |
| switches::kDisableBlinkFeatures, |
| base::JoinString(disabled_blink_features, ",")); |
| } |
| |
| command_line->AppendSwitch(switches::kUseFakeUIForMediaStream); |
| command_line->AppendSwitchASCII( |
| switches::kUseFakeDeviceForMediaStream, |
| base::StrCat({"display-media-type=", |
| DisplaySurfaceTypeAsString(display_surface_type_)})); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| } |
| |
| std::string GetVideoTrackType() { |
| return content::EvalJs(tab_->GetPrimaryMainFrame(), "getVideoTrackType();") |
| .ExtractString(); |
| } |
| |
| std::string GetVideoCloneTrackType() { |
| return content::EvalJs(tab_->GetPrimaryMainFrame(), |
| "getVideoCloneTrackType();") |
| .ExtractString(); |
| } |
| |
| bool HasAudioTrack() { |
| std::string result = |
| content::EvalJs(tab_->GetPrimaryMainFrame(), "hasAudioTrack();") |
| .ExtractString(); |
| EXPECT_TRUE(result == "true" || result == "false"); |
| return result == "true"; |
| } |
| |
| std::string GetAudioTrackType() { |
| return content::EvalJs(tab_->GetPrimaryMainFrame(), "getAudioTrackType();") |
| .ExtractString(); |
| } |
| |
| std::string ExpectedVideoTrackType() const { |
| switch (display_surface_type_) { |
| case DisplaySurfaceType::kTab: |
| return region_capture_enabled_ ? "BrowserCaptureMediaStreamTrack" |
| : "MediaStreamTrack"; |
| case DisplaySurfaceType::kWindow: |
| case DisplaySurfaceType::kScreen: |
| return "MediaStreamTrack"; |
| } |
| NOTREACHED(); |
| } |
| |
| protected: |
| const bool region_capture_enabled_; |
| const DisplaySurfaceType display_surface_type_; |
| |
| private: |
| raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> tab_ = nullptr; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| _, |
| GetDisplayMediaVideoTrackBrowserTest, |
| Combine(/*region_capture_enabled=*/Bool(), |
| /*display_surface_type=*/ |
| Values(DisplaySurfaceType::kTab, |
| DisplaySurfaceType::kWindow, |
| DisplaySurfaceType::kScreen)), |
| [](const testing::TestParamInfo< |
| GetDisplayMediaVideoTrackBrowserTest::ParamType>& info) { |
| return base::StrCat( |
| {std::get<0>(info.param) ? "RegionCapture" : "", |
| std::get<1>(info.param) == DisplaySurfaceType::kTab ? "Tab" |
| : std::get<1>(info.param) == DisplaySurfaceType::kWindow |
| ? "Window" |
| : "Screen"}); |
| }); |
| |
| // Normally, each of these these would have its own test, but the number of |
| // combinations and the setup time for browser-tests make this undesirable, |
| // especially given the simplicity of each of these tests. |
| // After both (a) Conditional Focus and (b) Region Capture ship, this can |
| // simpplified to three non-parameterized tests (tab/window/screen). |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaVideoTrackBrowserTest, RunCombinedTest) { |
| SetupTest(); |
| |
| // Test #1: The video track is of the expected type. |
| EXPECT_EQ(GetVideoTrackType(), ExpectedVideoTrackType()); |
| |
| // Test #2: Video clones are of the same type as the original. |
| EXPECT_EQ(GetVideoTrackType(), GetVideoCloneTrackType()); |
| |
| // Test #3: Audio tracks are all simply MediaStreamTrack. |
| if (HasAudioTrack()) { |
| EXPECT_EQ(GetAudioTrackType(), "MediaStreamTrack"); |
| } |
| } |
| |
| // Flaky on Mac, Windows, and ChromeOS bots, https://crbug.com/1371309 |
| // Also some flakes on Linux ASAN/MSAN builds. |
| #if BUILDFLAG(IS_LINUX) && \ |
| !(defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER)) |
| class GetDisplayMediaHiDpiBrowserTest |
| : public WebRtcTestBase, |
| public testing::WithParamInterface<TestConfigForMediaResolution> { |
| public: |
| GetDisplayMediaHiDpiBrowserTest() : test_config_(GetParam()) {} |
| |
| // The browser window size must be consistent with the |
| // INSTANTIATE_TEST_SUITE_P TestConfigForMediaResolution configurations below. |
| // See the comments there for more details. |
| static constexpr int kBrowserWindowWidth = 800; |
| static constexpr int kBrowserWindowHeight = 600; |
| |
| int constraint_width() const { return test_config_.constraint_width; } |
| int constraint_height() const { return test_config_.constraint_height; } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| WebRtcTestBase::SetUpInProcessBrowserTestFixture(); |
| DetectErrorsInJavaScript(); |
| } |
| |
| void SetUpOnMainThread() override { |
| WebRtcTestBase::SetUpOnMainThread(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Fire up the page. |
| tab_ = OpenTestPageInNewTab(kMainHtmlPage); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| WebRtcTestBase::SetUpCommandLine(command_line); |
| |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| |
| command_line->AppendSwitch(switches::kThisTabCaptureAutoAccept); |
| |
| command_line->AppendSwitchASCII( |
| switches::kWindowSize, |
| base::StringPrintf("%d,%d", kBrowserWindowWidth, kBrowserWindowHeight)); |
| |
| // Optionally, in case the test isn't working correctly, you can turn on |
| // debug logging for the feature to help track down problems. For example: |
| // command_line->AppendSwitchASCII(switches::kVModule, |
| // "*host_view*=1,*frame_tracker*=3"); |
| } |
| |
| std::string ResizeVideoForHiDpiCapture(int width, int height) { |
| return RunJs(base::StringPrintf("resizeVideoForHiDpiCapture(%d, %d);", |
| width, height)); |
| } |
| |
| double GetDevicePixelRatio() { |
| std::string result = RunJs("getDevicePixelRatio();"); |
| double device_pixel_ratio; |
| EXPECT_TRUE(base::StringToDouble(result, &device_pixel_ratio)); |
| return device_pixel_ratio; |
| } |
| |
| std::string GetDisplaySurfaceSetting() { |
| return RunJs("getDisplaySurfaceSetting();"); |
| } |
| |
| std::string GetLogicalSurfaceSetting() { |
| return RunJs("getLogicalSurfaceSetting();"); |
| } |
| |
| content::WebContents* Tab() const { return tab_; } |
| |
| private: |
| std::string RunJs(const std::string& command) { |
| return content::EvalJs(tab_->GetPrimaryMainFrame(), command) |
| .ExtractString(); |
| } |
| |
| const TestConfigForMediaResolution test_config_; |
| raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> tab_ = nullptr; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaHiDpiBrowserTest, Capture) { |
| ASSERT_EQ(GetDevicePixelRatio(), 1.0); |
| |
| // Initiate the capture. |
| RunGetDisplayMedia( |
| Tab(), |
| base::StringPrintf("{video: {width: {max: %d}, height: {max: %d}}, " |
| "preferCurrentTab: true}", |
| constraint_width(), constraint_height()), |
| /*is_fake_ui=*/false, /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| |
| // Ensure that the video is larger than the source tab to encourage use of a |
| // higher-resolution video stream. The size is arbitrary, but it should be |
| // significantly bigger than the switches::kWindowSize configured in this |
| // test's setup. |
| EXPECT_EQ(ResizeVideoForHiDpiCapture(kBrowserWindowWidth * 2, |
| kBrowserWindowHeight * 2), |
| "success"); |
| |
| EXPECT_EQ(GetDisplaySurfaceSetting(), "browser"); |
| |
| EXPECT_EQ(GetLogicalSurfaceSetting(), "true"); |
| |
| // The HiDPI scale change only occurs once the capture has actually started |
| // and the size information was propagated back to the browser process. |
| // Waiting for the video to start playing helps ensure that this is the case. |
| StartDetectingVideo(Tab(), "video"); |
| WaitForVideoToPlay(Tab()); |
| |
| // If the video size is higher resolution than the browser window |
| // size, expect that HiDPI mode should be active. |
| bool expect_hidpi = constraint_width() > kBrowserWindowWidth && |
| constraint_height() > kBrowserWindowHeight; |
| |
| double device_pixel_ratio = GetDevicePixelRatio(); |
| if (expect_hidpi) { |
| EXPECT_GT(device_pixel_ratio, 1.0); |
| EXPECT_LE(device_pixel_ratio, 2.0); |
| } else { |
| EXPECT_EQ(device_pixel_ratio, 1.0); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| GetDisplayMediaHiDpiBrowserTest, |
| // The test configurations use both large and small constraint sizes. The |
| // small constraint sizes must be smaller than the configured window size |
| // (cf. kBrowserWindowWidth and kBrowserWindowHeight in |
| // GetDisplayMediaHiDpiBrowserTest above), and the large sizes must be |
| // significantly larger than the browser window size. |
| Values(TestConfigForMediaResolution{/*constraint_width=*/640, |
| /*constraint_height=*/480}, |
| TestConfigForMediaResolution{/*constraint_width=*/3840, |
| /*constraint_height=*/2160})); |
| #endif |
| |
| class GetDisplayMediaChangeSourceBrowserTest |
| : public WebRtcTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool, bool>> { |
| public: |
| GetDisplayMediaChangeSourceBrowserTest() |
| : dynamic_surface_switching_requested_(std::get<0>(GetParam())), |
| feature_enabled_(std::get<1>(GetParam())), |
| user_shared_audio_(std::get<2>(GetParam())) {} |
| ~GetDisplayMediaChangeSourceBrowserTest() override = default; |
| |
| void SetUp() override { |
| // TODO(crbug.com/40245399): Fix GetDisplayMediaChangeSourceBrowserTest with |
| // audio requested on ChromeOS |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (dynamic_surface_switching_requested_ && feature_enabled_ && |
| user_shared_audio_) { |
| GTEST_SKIP(); |
| } |
| #endif |
| WebRtcTestBase::SetUp(); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| feature_list_.InitWithFeatureState( |
| media::kShareThisTabInsteadButtonGetDisplayMedia, feature_enabled_); |
| |
| WebRtcTestBase::SetUpInProcessBrowserTestFixture(); |
| |
| DetectErrorsInJavaScript(); |
| |
| base::FilePath test_dir; |
| ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir)); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| |
| if (!user_shared_audio_) { |
| command_line->AppendSwitch(switches::kTabCaptureAudioDefaultUnchecked); |
| } |
| } |
| |
| std::string GetConstraints() const { |
| return base::StringPrintf( |
| "{video: true, audio: true, surfaceSwitching: \"%s\"}", |
| dynamic_surface_switching_requested_ ? "include" : "exclude"); |
| } |
| |
| bool ShouldShowShareThisTabInsteadButton() const { |
| return dynamic_surface_switching_requested_ && feature_enabled_; |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| const bool dynamic_surface_switching_requested_; |
| const bool feature_enabled_; |
| const bool user_shared_audio_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| GetDisplayMediaChangeSourceBrowserTest, |
| Combine(Bool(), Bool(), Bool())); |
| |
| // TODO(crbug.com/40900706) Re-enable flaky test. |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaChangeSourceBrowserTest, |
| DISABLED_ChangeSource) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| content::WebContents* captured_tab = OpenTestPageInNewTab(kCapturedPageMain); |
| content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false, |
| /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| |
| EXPECT_TRUE(captured_tab->IsBeingCaptured()); |
| EXPECT_FALSE(other_tab->IsBeingCaptured()); |
| EXPECT_FALSE(capturing_tab->IsBeingCaptured()); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(captured_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| captured_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(capturing_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| if (!ShouldShowShareThisTabInsteadButton()) { |
| EXPECT_FALSE(HasShareThisTabInsteadButton(other_tab)); |
| return; |
| } |
| EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab), |
| kShareThisTabInsteadMessage); |
| |
| // Click the share-this-tab-instead secondary button. |
| GetDelegate(other_tab)->ShareThisTabInstead(); |
| |
| // Wait until the capture of the other tab has started. |
| while (!other_tab->IsBeingCaptured()) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| EXPECT_FALSE(captured_tab->IsBeingCaptured()); |
| EXPECT_TRUE(other_tab->IsBeingCaptured()); |
| EXPECT_FALSE(capturing_tab->IsBeingCaptured()); |
| EXPECT_EQ(GetShareThisTabInsteadButtonLabel(captured_tab), |
| kShareThisTabInsteadMessage); |
| EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| other_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(capturing_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| } |
| |
| // TODO(crbug.com/40900706) Re-enable flaky test. |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaChangeSourceBrowserTest, |
| DISABLED_ChangeSourceThenStopTracksRemovesIndicators) { |
| if (!ShouldShowShareThisTabInsteadButton()) { |
| GTEST_SKIP(); |
| } |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| OpenTestPageInNewTab(kCapturedPageMain); |
| content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false, |
| /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| |
| // Click the share-this-tab-instead secondary button. |
| GetDelegate(other_tab)->ShareThisTabInstead(); |
| |
| // Wait until the capture of the other tab has started. |
| while (!other_tab->IsBeingCaptured()) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| ASSERT_EQ(GetInfoBarManager(capturing_tab)->infobars().size(), 1u); |
| StopAllTracks(capturing_tab); |
| do { |
| base::RunLoop().RunUntilIdle(); |
| } while (GetInfoBarManager(capturing_tab)->infobars().size() > 0u); |
| } |
| |
| // TODO(crbug.com/40900706) Re-enable flaky test. |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaChangeSourceBrowserTest, |
| DISABLED_ChangeSourceReject) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| content::WebContents* captured_tab = OpenTestPageInNewTab(kCapturedPageMain); |
| content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false, |
| /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| |
| EXPECT_TRUE(captured_tab->IsBeingCaptured()); |
| EXPECT_FALSE(other_tab->IsBeingCaptured()); |
| EXPECT_FALSE(capturing_tab->IsBeingCaptured()); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(captured_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| captured_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(capturing_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| if (!ShouldShowShareThisTabInsteadButton()) { |
| EXPECT_FALSE(HasShareThisTabInsteadButton(other_tab)); |
| return; |
| } |
| EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab), |
| kShareThisTabInsteadMessage); |
| |
| browser()->tab_strip_model()->ActivateTabAt( |
| browser()->tab_strip_model()->GetIndexOfWebContents(other_tab)); |
| while (browser()->tab_strip_model()->GetActiveWebContents() != other_tab) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| browser()->profile()->GetPrefs()->SetBoolean(prefs::kScreenCaptureAllowed, |
| false); |
| |
| // Click the share-this-tab-instead secondary button. This is rejected since |
| // screen capture is not allowed by the above policy. |
| GetDelegate(other_tab)->ShareThisTabInstead(); |
| |
| // When "Share this tab instead" fails for other_tab, the focus goes back to |
| // the captured tab. Wait until that happens: |
| while (browser()->tab_strip_model()->GetActiveWebContents() != captured_tab) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| EXPECT_TRUE(captured_tab->IsBeingCaptured()); |
| EXPECT_FALSE(other_tab->IsBeingCaptured()); |
| EXPECT_FALSE(capturing_tab->IsBeingCaptured()); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(captured_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| captured_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab), |
| kShareThisTabInsteadMessage); |
| EXPECT_EQ( |
| GetShareThisTabInsteadButtonLabel(capturing_tab), |
| l10n_util::GetStringFUTF16( |
| IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON, |
| url_formatter::FormatOriginForSecurityDisplay( |
| capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(), |
| url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS))); |
| } |
| |
| class GetDisplayMediaSelfBrowserSurfaceBrowserTest |
| : public WebRtcTestBase, |
| public testing::WithParamInterface<std::string> { |
| public: |
| GetDisplayMediaSelfBrowserSurfaceBrowserTest() |
| : self_browser_surface_(GetParam()) {} |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| WebRtcTestBase::SetUpInProcessBrowserTestFixture(); |
| |
| DetectErrorsInJavaScript(); |
| |
| base::FilePath test_dir; |
| ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir)); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kMainHtmlTitle); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| } |
| |
| std::string GetConstraints(bool prefer_current_tab = false) { |
| std::vector<std::string> constraints = {"video: true"}; |
| if (!self_browser_surface_.empty()) { |
| constraints.push_back(base::StringPrintf("selfBrowserSurface: \"%s\"", |
| self_browser_surface_.c_str())); |
| } |
| if (prefer_current_tab) { |
| constraints.push_back("preferCurrentTab: true"); |
| } |
| prefer_current_tab_ = prefer_current_tab; |
| return "{" + base::JoinString(constraints, ",") + "}"; |
| } |
| |
| bool IsSelfBrowserSurfaceExclude() const { |
| if (self_browser_surface_ == "" && !prefer_current_tab_) { |
| // Special case - when using the new order, selfBrowserSurface |
| // defaults to "exclude", unless {preferCurrentTab: true} is specified. |
| return true; |
| } |
| return self_browser_surface_ == "exclude"; |
| } |
| |
| protected: |
| // If empty, the constraint is unused. Otherwise, the value is either |
| // "include" or "exclude" |
| const std::string self_browser_surface_; |
| |
| // Whether {preferCurrentTab: true} will be specified by the test. |
| bool prefer_current_tab_ = false; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| GetDisplayMediaSelfBrowserSurfaceBrowserTest, |
| Values("", "include", "exclude")); |
| |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaSelfBrowserSurfaceBrowserTest, |
| SelfBrowserSurfaceChangesCapturedTab) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // This test relies on |capturing_tab| appearing earlier in the media picker, |
| // and being auto-selected earlier if it is offered. |
| content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| // Success expected either way, with the *other* tab being captured |
| // when selfBrowserCapture is set to "exclude". |
| RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false, |
| /*expect_success=*/true, /*is_tab_capture=*/true); |
| |
| EXPECT_EQ(!IsSelfBrowserSurfaceExclude(), capturing_tab->IsBeingCaptured()); |
| EXPECT_EQ(IsSelfBrowserSurfaceExclude(), other_tab->IsBeingCaptured()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaSelfBrowserSurfaceBrowserTest, |
| SelfBrowserSurfaceInteractionWithPreferCurrentTab) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // This test relies on |capturing_tab| appearing earlier in the media picker, |
| // and being auto-selected earlier if it is offered. |
| content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| // Test focal point - getDisplayMedia() rejects if preferCurrentTab |
| // and exclude-current-tab are simultaneously specified. |
| // Note that preferCurrentTab is hard-coded in this test while |
| // exclude-current-tab is parameterized. |
| const bool expect_success = (self_browser_surface_ != "exclude"); |
| const std::string expected_error = |
| expect_success ? "" |
| : "TypeError: Failed to execute 'getDisplayMedia' on " |
| "'MediaDevices': Self-contradictory configuration " |
| "(preferCurrentTab and selfBrowserSurface=exclude)."; |
| RunGetDisplayMedia(capturing_tab, GetConstraints(/*prefer_current_tab=*/true), |
| /*is_fake_ui=*/false, expect_success, |
| /*is_tab_capture=*/true, expected_error); |
| |
| EXPECT_EQ(!IsSelfBrowserSurfaceExclude(), capturing_tab->IsBeingCaptured()); |
| EXPECT_FALSE(other_tab->IsBeingCaptured()); |
| } |
| |
| // Covers whether transient activation is required to call getDisplayMedia. |
| class GetDisplayMediaTransientActivationRequiredTest |
| : public WebRtcScreenCaptureBrowserTest, |
| public testing::WithParamInterface< |
| std::tuple<bool, bool, bool, std::optional<std::string>>> { |
| public: |
| GetDisplayMediaTransientActivationRequiredTest() |
| : with_user_gesture_(std::get<0>(GetParam())), |
| require_gesture_feature_enabled_(std::get<1>(GetParam())), |
| prefer_current_tab_(std::get<2>(GetParam())), |
| policy_allowlist_value_(std::get<3>(GetParam())) {} |
| ~GetDisplayMediaTransientActivationRequiredTest() override = default; |
| |
| static std::string GetDescription( |
| const testing::TestParamInfo< |
| GetDisplayMediaTransientActivationRequiredTest::ParamType>& info) { |
| std::string name = base::StrCat( |
| {std::get<0>(info.param) ? "WithUserGesture_" : "WithoutUserGesture_", |
| std::get<1>(info.param) ? "RequireGestureFeatureEnabled_" |
| : "_RequireGestureFeatureDisabled_", |
| std::get<2>(info.param) ? "PreferCurrentTab_" |
| : "DontPreferCurrentTab_", |
| std::get<3>(info.param).has_value() |
| ? (*std::get<3>(info.param) == kEmbeddedTestServerOrigin) |
| ? "Allowlisted" |
| : "OtherAllowlisted" |
| : "NoPolicySet"}); |
| return name; |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kUseFakeUIForMediaStream); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| WebRtcScreenCaptureBrowserTest::SetUpInProcessBrowserTestFixture(); |
| |
| if (require_gesture_feature_enabled_) { |
| feature_list_.InitAndEnableFeature( |
| blink::features::kGetDisplayMediaRequiresUserActivation); |
| } else { |
| feature_list_.InitAndDisableFeature( |
| blink::features::kGetDisplayMediaRequiresUserActivation); |
| } |
| |
| policy_provider_.SetDefaultReturns( |
| /*is_initialization_complete_return=*/true, |
| /*is_first_policy_load_complete_return=*/true); |
| policy::BrowserPolicyConnector::SetPolicyProviderForTesting( |
| &policy_provider_); |
| |
| DetectErrorsInJavaScript(); |
| } |
| |
| bool PreferCurrentTab() const override { return prefer_current_tab_; } |
| |
| protected: |
| const bool with_user_gesture_; |
| const bool require_gesture_feature_enabled_; |
| const bool prefer_current_tab_; |
| const std::optional<std::string> policy_allowlist_value_; |
| base::test::ScopedFeatureList feature_list_; |
| testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaTransientActivationRequiredTest, Check) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| if (policy_allowlist_value_.has_value()) { |
| policy::PolicyMap policy_map; |
| base::Value::List allowed_origins; |
| allowed_origins.Append(base::Value(*policy_allowlist_value_)); |
| policy_map.Set(policy::key::kScreenCaptureWithoutGestureAllowedForOrigins, |
| policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER, |
| policy::POLICY_SOURCE_PLATFORM, |
| base::Value(std::move(allowed_origins)), nullptr); |
| policy_provider_.UpdateChromePolicy(policy_map); |
| } |
| |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| const bool expect_success = |
| with_user_gesture_ || !require_gesture_feature_enabled_ || |
| (policy_allowlist_value_ && |
| *policy_allowlist_value_ == kEmbeddedTestServerOrigin); |
| const std::string expected_error = |
| expect_success |
| ? "" |
| : "InvalidStateError: Failed to execute 'getDisplayMedia' on " |
| "'MediaDevices': getDisplayMedia() requires transient activation " |
| "(user gesture)."; |
| |
| RunGetDisplayMedia(tab, GetConstraints(/*video=*/true, /*audio=*/true), |
| /*is_fake_ui=*/true, expect_success, |
| /*is_tab_capture=*/false, expected_error, |
| with_user_gesture_); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* no prefix */, |
| GetDisplayMediaTransientActivationRequiredTest, |
| Combine( |
| /*with_user_gesture=*/Bool(), |
| /*require_gesture_feature_enabled=*/Bool(), |
| /*prefer_current_tab=*/Bool(), |
| /*policy_allowlist_value=*/ |
| Values(std::nullopt, kEmbeddedTestServerOrigin, kOtherOrigin)), |
| &GetDisplayMediaTransientActivationRequiredTest::GetDescription); |
| |
| // Covers whether transient activation is conferred by the user's interaction |
| // with the prompt shown by getDisplayMedia. |
| class GetDisplayMediaConfersTransientActivationTest |
| : public WebRtcScreenCaptureBrowserTest, |
| public testing::WithParamInterface<std::tuple<bool, bool, bool>> { |
| public: |
| static std::string GetDescription( |
| const testing::TestParamInfo< |
| GetDisplayMediaConfersTransientActivationTest::ParamType>& info) { |
| const bool feature_enabled = std::get<0>(info.param); |
| const bool prefer_current_tab = std::get<1>(info.param); |
| const bool user_accepts = std::get<2>(info.param); |
| return base::StrCat( |
| {"WithFeature", feature_enabled ? "Enabled" : "Disabled", |
| prefer_current_tab ? "PreferCurrentTabVariant" : "StandardVariant", |
| "User", user_accepts ? "Accepts" : "Rejects", "Prompt"}); |
| } |
| |
| GetDisplayMediaConfersTransientActivationTest() |
| : feature_enabled_(std::get<0>(GetParam())), |
| prefer_current_tab_(std::get<1>(GetParam())), |
| user_accepts_(std::get<2>(GetParam())) {} |
| ~GetDisplayMediaConfersTransientActivationTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| if (prefer_current_tab_) { |
| command_line->AppendSwitch(user_accepts_ |
| ? switches::kThisTabCaptureAutoAccept |
| : switches::kThisTabCaptureAutoReject); |
| } else { |
| if (user_accepts_) { |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle); |
| } else { |
| command_line->AppendSwitch(switches::kCaptureAutoReject); |
| } |
| } |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| WebRtcScreenCaptureBrowserTest::SetUpInProcessBrowserTestFixture(); |
| feature_list_.InitWithFeatureState(media::kGetDisplayMediaConfersActivation, |
| feature_enabled_); |
| DetectErrorsInJavaScript(); |
| } |
| |
| bool PreferCurrentTab() const override { return prefer_current_tab_; } |
| |
| protected: |
| const bool feature_enabled_; |
| const bool prefer_current_tab_; |
| const bool user_accepts_; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| GetDisplayMediaConfersTransientActivationTest, |
| Combine( |
| /*feature_enabled=*/Bool(), |
| /*prefer_current_tab=*/Bool(), |
| /*user_accepts=*/Bool()), |
| &GetDisplayMediaConfersTransientActivationTest::GetDescription); |
| |
| // TODO(crbug.com/420406085): Re-enable the tests. |
| #if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) |
| #define MAYBE_GdmActivation_RunTest DISABLED_RunTest |
| #else |
| #define MAYBE_GdmActivation_RunTest RunTest |
| #endif |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaConfersTransientActivationTest, |
| MAYBE_GdmActivation_RunTest) { |
| // Setup |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| OpenTestPageInNewTab(kCapturedPageMain); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| ASSERT_FALSE( |
| capturing_tab->GetPrimaryMainFrame()->HasTransientUserActivation()); |
| |
| // `with_user_gesture` is set to `false` because `getDisplayMedia()` does not |
| // currently consume the activation (nor requires it). |
| RunGetDisplayMedia( |
| capturing_tab, |
| GetConstraints(/*video=*/true, /*audio=*/true, prefer_current_tab_), |
| /*is_fake_ui=*/false, /*expect_success=*/user_accepts_, |
| /*is_tab_capture=*/true, /*expected_error=*/"", |
| /*with_user_gesture=*/false); |
| |
| EXPECT_EQ(capturing_tab->GetPrimaryMainFrame()->HasTransientUserActivation(), |
| feature_enabled_ && user_accepts_); |
| } |
| |
| // This test suite ensures that, no matter the combination of inputs, |
| // an interaction with getUserMedia() does not confer transient activation. |
| // That is, the code authored for gDM does not mistrigger and run for gUM. |
| class GetUserMediaDoesNotConferTransientActivationTest |
| : public WebRtcTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool, bool>> { |
| public: |
| static std::string GetDescription( |
| const testing::TestParamInfo< |
| GetUserMediaDoesNotConferTransientActivationTest::ParamType>& info) { |
| const bool video = std::get<0>(info.param); |
| const bool audio = std::get<1>(info.param); |
| const bool user_accepts = std::get<2>(info.param); |
| return base::StrCat({"Video", video ? "On" : "Off", "Audio", |
| audio ? "On" : "Off", "User", |
| user_accepts ? "Accepts" : "Rejects", "Prompt"}); |
| } |
| |
| GetUserMediaDoesNotConferTransientActivationTest() |
| : video_(std::get<0>(GetParam())), |
| audio_(std::get<1>(GetParam())), |
| user_accepts_(std::get<2>(GetParam())) {} |
| ~GetUserMediaDoesNotConferTransientActivationTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| WebRtcTestBase::SetUpInProcessBrowserTestFixture(); |
| DetectErrorsInJavaScript(); |
| } |
| |
| protected: |
| const bool video_; |
| const bool audio_; |
| const bool user_accepts_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| GetUserMediaDoesNotConferTransientActivationTest, |
| Combine( |
| /*video=*/Bool(), |
| /*audio=*/Bool(), |
| /*user_accepts=*/Bool()), |
| &GetUserMediaDoesNotConferTransientActivationTest::GetDescription); |
| |
| // TODO(crbug.com/420406085): Re-enable the tests. |
| #if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) |
| #define MAYBE_GumActivation_RunTest DISABLED_RunTest |
| #else |
| #define MAYBE_GumActivation_RunTest RunTest |
| #endif |
| IN_PROC_BROWSER_TEST_P(GetUserMediaDoesNotConferTransientActivationTest, |
| MAYBE_GumActivation_RunTest) { |
| if (!video_ && !audio_) { |
| GTEST_SKIP(); |
| } |
| |
| // Setup |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| content::WebContents* const wc = OpenTestPageInNewTab(kMainHtmlPage); |
| permissions::PermissionRequestManager::FromWebContents(wc) |
| ->set_auto_response_for_test( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| ASSERT_FALSE(wc->GetPrimaryMainFrame()->HasTransientUserActivation()); |
| |
| const std::string constraints = |
| base::StringPrintf("{video: %s, audio: %s}", video_ ? "true" : "false", |
| audio_ ? "true" : "false"); |
| RunGetUserMedia(wc, constraints); |
| |
| EXPECT_FALSE(wc->GetPrimaryMainFrame()->HasTransientUserActivation()); |
| } |
| |
| // Encapsulates information about a capture-session in which one tab starts |
| // out capturing a specific other tab, and later possibly moves to capturing |
| // another tab. The encapsulation of this state allows for more succinct tests, |
| // especially when testing multiple concurrent capture-sessions. |
| class CaptureSessionDetails { |
| public: |
| enum class CapturedTab { |
| kInitiallyCapturedTab, |
| kOtherTab, |
| kCapturingTab, // Share-this-tab-instead can cause self-capture. |
| }; |
| |
| CaptureSessionDetails(std::string session_name, |
| WebContents* initially_captured_tab, |
| WebContents* other_tab, |
| WebContents* capturing_tab) |
| : session_name_(std::move(session_name)), |
| initially_captured_tab_(initially_captured_tab), |
| other_tab_(other_tab), |
| capturing_tab_(capturing_tab) {} |
| |
| std::unique_ptr<MockCapturedSurfaceController> |
| MakeMockCapturedSurfaceController( |
| blink::mojom::CapturedSurfaceControlResult permission_response, |
| GlobalRenderFrameHostId gdm_rfhid, |
| WebContentsMediaCaptureId captured_wc_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| EXPECT_FALSE(mock_captured_surface_controller_) |
| << "Instantiated more CapturedSurfaceController than expected."; |
| |
| mock_captured_surface_controller_ = |
| new MockCapturedSurfaceController(gdm_rfhid, captured_wc_id); |
| mock_captured_surface_controller_->SetRequestPermissionResponse( |
| permission_response); |
| |
| return base::WrapUnique(mock_captured_surface_controller_.get()); |
| } |
| |
| void RunGetDisplayMedia() { |
| ::RunGetDisplayMedia(capturing_tab_, |
| "{video: true, surfaceSwitching: \"include\"}", |
| /*is_fake_ui=*/false, |
| /*expect_success=*/true, |
| /*is_tab_capture=*/true); |
| } |
| |
| // Sets a factory that produces mock controllers for Captured Surface Control |
| // and attaches them to `this` CaptureSessionDetails object. They will respond |
| // to permission checks with the preconfigured `permission_response`. |
| // |
| // The factory is global. Tests that instantiate multiple capture sessions |
| // should make sure to call this again from the new CaptureSessionDetails |
| // object at the appropriate time, thereby replacing the factory after it's |
| // used. |
| // |
| // This method is called on the UI thread. Hops to the IO thread and sets the |
| // CSC-factory, then unblocks execution on the UI thread. |
| void SetCapturedSurfaceControllerFactory( |
| blink::mojom::CapturedSurfaceControlResult permission_response = |
| blink::mojom::CapturedSurfaceControlResult::kSuccess) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::RunLoop run_loop; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CaptureSessionDetails::SetCapturedSurfaceControllerFactoryOnIO, |
| base::Unretained(this), permission_response, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| void SetExpectUpdateCaptureTarget() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::RunLoop run_loop; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CaptureSessionDetails::ExpectUpdateCaptureTargetOnIO, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| void VerifyAndClearExpectations() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| SCOPED_TRACE(session_name_); |
| |
| base::RunLoop run_loop; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CaptureSessionDetails::VerifyAndClearExpectationsOnIO, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| WebContents* GetTab(CapturedTab captured_tab) { |
| switch (captured_tab) { |
| case CapturedTab::kInitiallyCapturedTab: |
| return initially_captured_tab_; |
| case CapturedTab::kOtherTab: |
| return other_tab_; |
| case CapturedTab::kCapturingTab: |
| return capturing_tab_; |
| } |
| NOTREACHED(); |
| } |
| |
| WebContents* GetCapturedTab() { |
| CHECK_EQ(static_cast<int>(capturing_tab_->IsBeingCaptured()) + |
| static_cast<int>(initially_captured_tab_->IsBeingCaptured()) + |
| static_cast<int>(other_tab_->IsBeingCaptured()), |
| 1); |
| if (capturing_tab_->IsBeingCaptured()) { |
| return capturing_tab_; |
| } else if (initially_captured_tab_->IsBeingCaptured()) { |
| return initially_captured_tab_; |
| } else if (other_tab_->IsBeingCaptured()) { |
| return other_tab_; |
| } |
| NOTREACHED(); |
| } |
| |
| // Get the tab that's neither capturing nor being captured. |
| WebContents* GetNonCapturedTab() { |
| CHECK(!capturing_tab_->IsBeingCaptured()); |
| CHECK_EQ(static_cast<int>(initially_captured_tab_->IsBeingCaptured()) + |
| static_cast<int>(other_tab_->IsBeingCaptured()), |
| 1); |
| |
| return initially_captured_tab_->IsBeingCaptured() ? other_tab_ |
| : initially_captured_tab_; |
| } |
| |
| void WaitForCaptureOf(CapturedTab expected_tab) { |
| while (!GetTab(expected_tab)->IsBeingCaptured()) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| ExpectCapturedTab(expected_tab); |
| } |
| |
| void ExpectCapturedTab(CapturedTab captured) { |
| EXPECT_EQ(initially_captured_tab_->IsBeingCaptured(), |
| captured == CapturedTab::kInitiallyCapturedTab); |
| EXPECT_EQ(other_tab_->IsBeingCaptured(), |
| captured == CapturedTab::kOtherTab); |
| EXPECT_EQ(capturing_tab_->IsBeingCaptured(), |
| captured == CapturedTab::kCapturingTab); |
| |
| EXPECT_EQ(GetShareThisTabInsteadButtonLabel(GetNonCapturedTab()), |
| kShareThisTabInsteadMessage); |
| } |
| |
| // Forwards from the target element, or stops forwarding if target is "null". |
| std::string ForwardWheel(const std::string& target) { |
| return content::EvalJs( |
| capturing_tab_->GetPrimaryMainFrame(), |
| base::StringPrintf("forwardWheel(%s);", target.c_str())) |
| .ExtractString(); |
| } |
| |
| void UpdateZoomLevel(const std::string& action, bool expect_success = true) { |
| const std::string command = |
| base::StringPrintf("updateZoomLevel(\"%s\");", action); |
| const std::string expected_result = base::StringPrintf( |
| "%s-zoom-level-%s", action, expect_success ? "resolved" : "error"); |
| |
| EXPECT_EQ(content::EvalJs(capturing_tab_->GetPrimaryMainFrame(), command), |
| expected_result); |
| } |
| |
| std::optional<int> GetZoomLevel() { |
| const content::EvalJsResult result = content::EvalJs( |
| capturing_tab_->GetPrimaryMainFrame(), "getZoomLevel();"); |
| return (result == base::Value()) |
| ? std::nullopt |
| : std::make_optional<int>(result.ExtractInt()); |
| } |
| |
| // Call `controller.getSupportedZoomLevels()`. |
| // Returns the result if successful; the error otherwise. |
| base::expected<std::vector<int>, std::string> GetSupportedZoomLevels() { |
| content::EvalJsResult js_result = content::EvalJs( |
| capturing_tab_->GetPrimaryMainFrame(), "getSupportedZoomLevels();"); |
| |
| const base::Value::List& list = js_result.ExtractList(); |
| EXPECT_GE(list.size(), 1u); |
| if (list.size() == 1u) { |
| // Reserved for an error. |
| return base::unexpected(list[0].GetString()); |
| } |
| |
| std::vector<int> result; |
| result.reserve(list.size()); |
| for (const base::Value& val : list) { |
| EXPECT_TRUE(val.is_int()); |
| result.push_back(val.GetInt()); |
| } |
| return result; |
| } |
| |
| int GetZoomLevelChangeEventsSinceLast() { |
| // Note that ExtractInt() will implicitly ensure the script did not run into |
| // an error. |
| return content::EvalJs(capturing_tab_->GetPrimaryMainFrame(), |
| "zoomLevelChangeEventsSinceLast();") |
| .ExtractInt(); |
| } |
| |
| WebContents* initially_captured_tab() const { |
| return initially_captured_tab_; |
| } |
| WebContents* other_tab() const { return other_tab_; } |
| WebContents* capturing_tab() const { return capturing_tab_; } |
| |
| private: |
| void SetCapturedSurfaceControllerFactoryOnIO( |
| blink::mojom::CapturedSurfaceControlResult permission_response, |
| base::RepeatingClosure done_closure) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| CapturedSurfaceControllerFactoryCallback factory = base::BindRepeating( |
| &CaptureSessionDetails::MakeMockCapturedSurfaceController, |
| base::Unretained(this), permission_response); |
| |
| content::SetCapturedSurfaceControllerFactoryForTesting(factory); |
| |
| done_closure.Run(); |
| } |
| |
| void ExpectUpdateCaptureTargetOnIO(base::RepeatingClosure done_closure) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| CHECK(mock_captured_surface_controller_); |
| EXPECT_CALL(*mock_captured_surface_controller_, UpdateCaptureTarget(_)) |
| .Times(1); |
| |
| done_closure.Run(); |
| } |
| |
| void VerifyAndClearExpectationsOnIO(base::RepeatingClosure done_closure) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| CHECK(mock_captured_surface_controller_); |
| Mock::VerifyAndClearExpectations(mock_captured_surface_controller_); |
| mock_captured_surface_controller_ = nullptr; |
| |
| done_closure.Run(); |
| } |
| |
| const std::string session_name_; |
| |
| // Handled on UI thread. |
| const raw_ptr<WebContents> initially_captured_tab_; |
| const raw_ptr<WebContents> other_tab_; |
| const raw_ptr<WebContents> capturing_tab_; |
| |
| // Handled on the IO thread. |
| raw_ptr<MockCapturedSurfaceController> mock_captured_surface_controller_; |
| }; |
| |
| class CapturedSurfaceControlTest : public WebRtcTestBase { |
| public: |
| enum class Action { |
| kForwardWheel, // forwardWheel(validElement) |
| kForwardWheelNull, // forwardWheel(null) |
| kIncreaseZoomLevel, |
| kDecreaseZoomLevel, |
| kResetZoomLevel, |
| kGetZoomLevel, |
| kGetSupportedZoomLevels, |
| }; |
| |
| static const char* ToZoomLevelAction(Action input) { |
| switch (input) { |
| case Action::kIncreaseZoomLevel: |
| return "increase"; |
| case Action::kDecreaseZoomLevel: |
| return "decrease"; |
| case Action::kResetZoomLevel: |
| return "reset"; |
| case Action::kForwardWheel: |
| case Action::kForwardWheelNull: |
| case Action::kGetZoomLevel: |
| case Action::kGetSupportedZoomLevels: |
| break; |
| } |
| NOTREACHED() << "Not a ZoomLevelAction."; |
| } |
| |
| static bool ShouldTriggerCscIndicator(Action action) { |
| switch (action) { |
| case Action::kForwardWheel: |
| case Action::kIncreaseZoomLevel: |
| case Action::kDecreaseZoomLevel: |
| case Action::kResetZoomLevel: |
| return true; |
| case Action::kGetZoomLevel: |
| case Action::kGetSupportedZoomLevels: |
| case Action::kForwardWheelNull: |
| return false; |
| } |
| NOTREACHED(); |
| } |
| |
| static void MakeValidApiCall(CaptureSessionDetails& capture_session, |
| Action action) { |
| switch (action) { |
| case Action::kForwardWheel: |
| EXPECT_EQ(capture_session.ForwardWheel("video"), |
| "forward-wheel-resolved"); |
| return; |
| case Action::kForwardWheelNull: |
| EXPECT_EQ(capture_session.ForwardWheel("null"), |
| "forward-wheel-resolved"); |
| return; |
| case Action::kIncreaseZoomLevel: |
| case Action::kDecreaseZoomLevel: |
| case Action::kResetZoomLevel: |
| capture_session.UpdateZoomLevel(ToZoomLevelAction(action)); |
| return; |
| case Action::kGetZoomLevel: |
| capture_session.GetZoomLevel(); |
| return; |
| case Action::kGetSupportedZoomLevels: |
| (void)capture_session.GetSupportedZoomLevels(); |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| CapturedSurfaceControlTest() = default; |
| ~CapturedSurfaceControlTest() override = default; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| feature_list_.InitWithFeatures( |
| /*enabled_features=*/{media::kShareThisTabInsteadButtonGetDisplayMedia, |
| blink::features::kCapturedSurfaceControl}, |
| /*disabled_features=*/{}); |
| |
| WebRtcTestBase::SetUpInProcessBrowserTestFixture(); |
| |
| DetectErrorsInJavaScript(); |
| |
| base::FilePath test_dir; |
| ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir)); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitchASCII( |
| switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle); |
| command_line->AppendSwitch( |
| switches::kAutoGrantCapturedSurfaceControlPrompt); |
| |
| AdjustCommandLineForZeroCopyCapture(command_line); |
| } |
| |
| // Runs the `ChangeSourceWorksOnCorrectCaptureSession` test. |
| // This is defined as a method in order to test both the first/second |
| // capture experiencing the share-this-tab-instead click, without having |
| // to parameterize the entire test suite. |
| void RunChangeSourceWorksOnCorrectCaptureSession( |
| size_t session_experiencing_change); |
| |
| // Runs the `ChangingCapturedTabZoomChangeEventTest` test. |
| // This is defined as a method in order to test both of the following tests |
| // without the overhead and unclarity of parameterizing a test suite for it. |
| // * ChangingCapturedTabIssuesEventIfDifferentZoomLevels |
| // * ChangingCapturedTabDoesNotIssueEventIfSameZoomLevels |
| void RunChangingCapturedTabZoomChangeEventTest(double zoom_level_first_tab, |
| double zoom_level_second_tab); |
| |
| protected: |
| using CapturedTab = ::CaptureSessionDetails::CapturedTab; |
| |
| CaptureSessionDetails MakeCaptureSessionDetails(std::string session_name) { |
| return CaptureSessionDetails( |
| std::move(session_name), |
| /*initially_captured_tab=*/OpenTestPageInNewTab(kCapturedPageMain), |
| /*other_tab=*/(OpenTestPageInNewTab(kMainHtmlPage)), |
| /*capturing_tab=*/(OpenTestPageInNewTab(kMainHtmlPage))); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| using CscAction = CapturedSurfaceControlTest::Action; |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| UnboundCaptureControllerReportNullZoomLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Note absence of call to RunGetDisplayMedia(). |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| EXPECT_EQ(capture_session.GetZoomLevel(), std::nullopt); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| CorrectlyReportDefaultCapturedSurfaceZoomLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Note absence of call to SetZoomFactor(). |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| WebContents* const captured_tab = capture_session.GetCapturedTab(); |
| EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 100); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| CorrectlyReportNonDefaultCapturedSurfaceZoomLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| // Set the zoom factor to something other than the default before |
| // capture starts. |
| WebContents* const captured_tab = |
| capture_session.GetTab(CapturedTab::kInitiallyCapturedTab); |
| SetZoomFactor(captured_tab, 0.5); |
| |
| // Start the capture. |
| capture_session.RunGetDisplayMedia(); |
| ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab); |
| |
| // The initially reported zoom level is as expected. |
| EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 50); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| GetSupportedZoomLevelsFailsOnUnboundCaptureController) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Note absence of call to RunGetDisplayMedia(). |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| const base::expected<std::vector<int>, std::string> result = |
| capture_session.GetSupportedZoomLevels(); |
| EXPECT_FALSE(result.has_value()); |
| EXPECT_NE(result.error().find("InvalidStateError"), std::string::npos); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| GetSupportedZoomLevelsSucceedsIfCapturingTab) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| EXPECT_TRUE(capture_session.GetSupportedZoomLevels().has_value()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| GetSupportedZoomLevelsMonotonouslyIncreasing) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| const base::expected<std::vector<int>, std::string> result = |
| capture_session.GetSupportedZoomLevels(); |
| EXPECT_TRUE(result.has_value()); |
| const std::vector<int>& values = result.value(); |
| ASSERT_GE(values.size(), 2u); |
| for (size_t i = 0; i < values.size() - 1; ++i) { |
| EXPECT_GT(values[i + 1], values[i]); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| GetSupportedZoomLevelsFailsIfTracksStopped) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| ASSERT_TRUE(capture_session.GetSupportedZoomLevels().has_value()); |
| |
| StopAllTracks(capture_session.capturing_tab()); |
| |
| const base::expected<std::vector<int>, std::string> result = |
| capture_session.GetSupportedZoomLevels(); |
| EXPECT_FALSE(result.has_value()); |
| EXPECT_NE(result.error().find("InvalidStateError"), std::string::npos); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| CapturedSurfaceControlTest, |
| NoZoomLevelChangeEventFiredWhenCaptureStartsWithDefaultZoomLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Note absence of call to SetZoomFactor(). |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| CapturedSurfaceControlTest, |
| NoZoomLevelChangeEventFiredWhenCaptureStartsWithNonDefaultZoomLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| // Set the zoom factor to something other than the default before |
| // capture starts. |
| WebContents* const captured_tab = |
| capture_session.GetTab(CapturedTab::kInitiallyCapturedTab); |
| SetZoomFactor(captured_tab, 0.5); |
| |
| capture_session.RunGetDisplayMedia(); |
| ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab); |
| |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| IncreaseZoomLevelSucceedsBelowMaxValue) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| WebContents* const captured_tab = capture_session.GetCapturedTab(); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 100); |
| |
| capture_session.UpdateZoomLevel("increase"); |
| |
| // Check both the actual zoom level as well as the one reported to JS. |
| const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab); |
| EXPECT_GT(actual_zoom_level_percent, 100); |
| EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| IncreaseZoomLevelFailsAtMaxValue) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| WebContents* const captured_tab = capture_session.GetCapturedTab(); |
| const double max_factor = blink::kPresetBrowserZoomFactors.back(); |
| SetZoomFactor(captured_tab, max_factor); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), std::round(100 * max_factor)); |
| |
| capture_session.UpdateZoomLevel("increase", /*expect_success=*/false); |
| |
| // Check both the actual zoom level as well as the one reported to JS. |
| const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab); |
| EXPECT_EQ(actual_zoom_level_percent, std::round(100 * max_factor)); |
| EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| IncreaseZoomLevelIssuesEvent) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| capture_session.UpdateZoomLevel("increase"); |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| DecreaseZoomLevelSucceedsAboveMinValue) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| WebContents* const captured_tab = capture_session.GetCapturedTab(); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 100); |
| |
| capture_session.UpdateZoomLevel("decrease"); |
| |
| // Check both the actual zoom level as well as the one reported to JS. |
| const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab); |
| EXPECT_LT(actual_zoom_level_percent, 100); |
| EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| DecreaseZoomLevelFailsAtMinValue) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| WebContents* const captured_tab = capture_session.GetCapturedTab(); |
| const double min_factor = blink::kPresetBrowserZoomFactors.front(); |
| SetZoomFactor(captured_tab, min_factor); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), std::round(100 * min_factor)); |
| |
| capture_session.UpdateZoomLevel("decrease", /*expect_success=*/false); |
| |
| // Check both the actual zoom level as well as the one reported to JS. |
| const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab); |
| EXPECT_EQ(actual_zoom_level_percent, std::round(100 * min_factor)); |
| EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| DecreaseZoomLevelIssuesEvent) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| |
| capture_session.UpdateZoomLevel("decrease"); |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 1); |
| } |
| |
| // The "expected" case of resetZoomLevel() - changing *back* to |
| // the default value. |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ResetZoomLevelSucceedsIfNonDefaultLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| // Set the zoom factor to something other than the default before |
| // capture starts. |
| WebContents* const captured_tab = |
| capture_session.GetTab(CapturedTab::kInitiallyCapturedTab); |
| SetZoomFactor(captured_tab, 0.5); |
| |
| // Start the capture. |
| capture_session.RunGetDisplayMedia(); |
| ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 50); |
| |
| // Reset works as expected. |
| capture_session.UpdateZoomLevel("reset"); |
| |
| // Check both the actual zoom level as well as the one reported to JS. |
| EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 100); |
| EXPECT_EQ(capture_session.GetZoomLevel(), 100); |
| } |
| |
| // The less "expected" case of resetZoomLevel() - calling reset...() |
| // when already at the default value. Should be no-op but succeed. |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ResetZoomLevelSucceedsIfDefaultLevel) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| capture_session.RunGetDisplayMedia(); |
| WebContents* const captured_tab = capture_session.GetCapturedTab(); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 100); |
| |
| // Reset works as expected. |
| capture_session.UpdateZoomLevel("reset"); |
| |
| // Check both the actual zoom level as well as the one reported to JS. |
| EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 100); |
| EXPECT_EQ(capture_session.GetZoomLevel(), 100); |
| } |
| |
| void CapturedSurfaceControlTest::RunChangingCapturedTabZoomChangeEventTest( |
| double zoom_level_first_tab, |
| double zoom_level_second_tab) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| WebContents* const first_captured_tab = |
| capture_session.GetTab(CapturedTab::kInitiallyCapturedTab); |
| SetZoomFactor(first_captured_tab, zoom_level_first_tab); |
| |
| WebContents* const second_captured_tab = |
| capture_session.GetTab(CapturedTab::kOtherTab); |
| SetZoomFactor(second_captured_tab, zoom_level_second_tab); |
| |
| capture_session.RunGetDisplayMedia(); |
| ASSERT_EQ(GetZoomLevelPercentage(first_captured_tab), |
| 100 * zoom_level_first_tab); |
| ASSERT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0); |
| |
| GetDelegate(second_captured_tab)->ShareThisTabInstead(); |
| capture_session.WaitForCaptureOf(CapturedTab::kOtherTab); |
| ASSERT_EQ(capture_session.GetCapturedTab(), second_captured_tab); |
| ASSERT_EQ(GetZoomLevelPercentage(second_captured_tab), |
| 100 * zoom_level_second_tab); |
| |
| const int expected_event_count = |
| (zoom_level_first_tab != zoom_level_second_tab) ? 1 : 0; |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), |
| expected_event_count); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ChangingCapturedTabIssuesEventIfDifferentZoomLevels) { |
| SCOPED_TRACE("ChangingCapturedTabIssuesEventIfDifferentZoomLevels"); |
| RunChangingCapturedTabZoomChangeEventTest(0.5, 0.75); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ChangingCapturedTabDoesNotIssueEventIfSameZoomLevels) { |
| SCOPED_TRACE("ChangingCapturedTabDoesNotIssueEventIfSameZoomLevels"); |
| RunChangingCapturedTabZoomChangeEventTest(0.5, 0.5); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ResetZoomLevelOnlyIssuesEventsWhenZoomLevelChanges) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| |
| // Set the zoom factor to something other than the default before |
| // capture starts. |
| WebContents* const captured_tab = |
| capture_session.GetTab(CapturedTab::kInitiallyCapturedTab); |
| SetZoomFactor(captured_tab, 0.5); |
| |
| // Start the capture. |
| capture_session.RunGetDisplayMedia(); |
| ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab); |
| ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 50); |
| ASSERT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0); |
| |
| // Expectation #1 - the initial reset issues an event. |
| capture_session.UpdateZoomLevel("reset"); |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 1); |
| |
| // Expectation #2 - additional resets don't issue an event. |
| capture_session.UpdateZoomLevel("reset"); |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0); |
| |
| // Expectation #3 - events still generally issued, they just |
| // require actual change of zoom level. (Test is sane.) |
| capture_session.UpdateZoomLevel("increase"); |
| capture_session.UpdateZoomLevel("reset"); |
| EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ChangeSourceTriggersUpdateCaptureTarget) { |
| SCOPED_TRACE("ChangeSourceTriggersUpdateCaptureTarget"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.SetCapturedSurfaceControllerFactory(); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| EXPECT_EQ(capture_session.ForwardWheel("video"), "forward-wheel-resolved"); |
| |
| // Expect that clicking "share this tab instead" will pipe a notification of |
| // the change to the captured surface controller. |
| capture_session.SetExpectUpdateCaptureTarget(); |
| GetDelegate(capture_session.other_tab())->ShareThisTabInstead(); |
| capture_session.WaitForCaptureOf(CapturedTab::kOtherTab); |
| |
| capture_session.VerifyAndClearExpectations(); |
| } |
| |
| void CapturedSurfaceControlTest::RunChangeSourceWorksOnCorrectCaptureSession( |
| size_t session_experiencing_change) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session_0 = |
| MakeCaptureSessionDetails("capture_session_0"); |
| capture_session_0.SetCapturedSurfaceControllerFactory(); |
| capture_session_0.RunGetDisplayMedia(); |
| capture_session_0.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| EXPECT_EQ(capture_session_0.ForwardWheel("video"), "forward-wheel-resolved"); |
| |
| CaptureSessionDetails capture_session_1 = |
| MakeCaptureSessionDetails("capture_session_1"); |
| capture_session_1.SetCapturedSurfaceControllerFactory(); |
| capture_session_1.RunGetDisplayMedia(); |
| capture_session_1.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| EXPECT_EQ(capture_session_1.ForwardWheel("video"), "forward-wheel-resolved"); |
| |
| // Expect that clicking "share this tab instead" will pipe a notification of |
| // the change to the correct CapturedSurfaceController. |
| CHECK(session_experiencing_change == 0 || session_experiencing_change == 1); |
| CaptureSessionDetails& capture_session_experiencing_change = |
| (session_experiencing_change == 0) ? capture_session_0 |
| : capture_session_1; |
| capture_session_experiencing_change.SetExpectUpdateCaptureTarget(); |
| GetDelegate(capture_session_experiencing_change.other_tab(), |
| /*infobar_index=*/session_experiencing_change) |
| ->ShareThisTabInstead(); |
| capture_session_experiencing_change.WaitForCaptureOf(CapturedTab::kOtherTab); |
| |
| capture_session_0.VerifyAndClearExpectations(); |
| capture_session_1.VerifyAndClearExpectations(); |
| } |
| |
| // Test when the first of two capture sessions experiences the source-change. |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ChangeSourceWorksOnCorrectCaptureSession0) { |
| SCOPED_TRACE("ChangeSourceWorksOnCorrectCaptureSession0"); |
| RunChangeSourceWorksOnCorrectCaptureSession(0); |
| } |
| |
| // Test when the second of two capture sessions experiences the source-change. |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ChangeSourceWorksOnCorrectCaptureSession1) { |
| SCOPED_TRACE("ChangeSourceWorksOnCorrectCaptureSession1"); |
| RunChangeSourceWorksOnCorrectCaptureSession(1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ForwardWheelElementFailsIfNoPermission) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.SetCapturedSurfaceControllerFactory( |
| blink::mojom::CapturedSurfaceControlResult::kNoPermissionError); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| EXPECT_THAT(capture_session.ForwardWheel("video"), |
| HasSubstr("NotAllowedError")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest, |
| ForwardWheelNullSucceedsWithoutPermission) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.SetCapturedSurfaceControllerFactory( |
| blink::mojom::CapturedSurfaceControlResult::kNoPermissionError); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| EXPECT_EQ(capture_session.ForwardWheel("null"), "forward-wheel-resolved"); |
| } |
| |
| class CapturedSurfaceControlIndicatorTest |
| : public CapturedSurfaceControlTest, |
| public testing::WithParamInterface<CscAction> { |
| public: |
| CapturedSurfaceControlIndicatorTest() : action_(GetParam()) {} |
| ~CapturedSurfaceControlIndicatorTest() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| CapturedSurfaceControlTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch( |
| switches::kAutoGrantCapturedSurfaceControlPrompt); |
| } |
| |
| protected: |
| const CscAction action_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| CapturedSurfaceControlIndicatorTest, |
| Values(CscAction::kForwardWheel, |
| CscAction::kForwardWheelNull, |
| CscAction::kIncreaseZoomLevel, |
| CscAction::kDecreaseZoomLevel, |
| CscAction::kResetZoomLevel, |
| CscAction::kGetZoomLevel, |
| CscAction::kGetSupportedZoomLevels)); |
| |
| IN_PROC_BROWSER_TEST_P(CapturedSurfaceControlIndicatorTest, |
| IndicatorNotShownBeforeApiInvocation) { |
| SCOPED_TRACE("IndicatorNotShownBeforeApiInvocation"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| // The CSC indicator is not shown anywhere. |
| EXPECT_FALSE(HasCscIndicator(capture_session.capturing_tab())); |
| EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab())); |
| EXPECT_FALSE(HasCscIndicator(capture_session.other_tab())); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CapturedSurfaceControlIndicatorTest, |
| IndicatorShownAfterWriteAccessApiInvocation) { |
| SCOPED_TRACE("IndicatorShownAfterWriteAccessApiInvocation"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| MakeValidApiCall(capture_session, action_); |
| |
| // The capturing tab's infobar shows the CSC indicator, but only |
| // if the action was a write-access action. |
| EXPECT_EQ(HasCscIndicator(capture_session.capturing_tab()), |
| ShouldTriggerCscIndicator(action_)); |
| |
| // The CSC indicator is not shown on any other infobar. |
| EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab())); |
| EXPECT_FALSE(HasCscIndicator(capture_session.other_tab())); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| CapturedSurfaceControlIndicatorTest, |
| IndicatorStateRetainedAfterShareThisTabInsteadNoCscBefore) { |
| SCOPED_TRACE("IndicatorStateRetainedAfterShareThisTabInsteadNoCscBefore"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| // Note absence of call to MakeValidApiCall() before share-this-tab-instead. |
| GetDelegate(capture_session.other_tab())->ShareThisTabInstead(); |
| |
| // The capturing tab's infobar does not show the CSC indicator because |
| // a write-access CSC action was not invoked. |
| EXPECT_FALSE(HasCscIndicator(capture_session.capturing_tab())); |
| |
| // The CSC indicator is not shown on any other infobar. |
| EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab())); |
| EXPECT_FALSE(HasCscIndicator(capture_session.other_tab())); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| CapturedSurfaceControlIndicatorTest, |
| IndicatorStateRetainedAfterShareThisTabInsteadAfterCscAction) { |
| SCOPED_TRACE("IndicatorStateRetainedAfterShareThisTabInsteadAfterCscAction"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| CaptureSessionDetails capture_session = |
| MakeCaptureSessionDetails("capture_session"); |
| capture_session.RunGetDisplayMedia(); |
| capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab); |
| |
| MakeValidApiCall(capture_session, action_); |
| GetDelegate(capture_session.other_tab())->ShareThisTabInstead(); |
| |
| // The capturing tab's infobar show the CSC indicator if the action |
| // was a write-access action. |
| EXPECT_EQ(HasCscIndicator(capture_session.capturing_tab()), |
| ShouldTriggerCscIndicator(action_)); |
| |
| // The CSC indicator is not shown on any other infobar. |
| EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab())); |
| EXPECT_FALSE(HasCscIndicator(capture_session.other_tab())); |
| } |
| |
| class WebRtcScreenCaptureBrowserTestUserRejection |
| : public WebRtcScreenCaptureBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| WebRtcScreenCaptureBrowserTestUserRejection() |
| : prefer_current_tab_(GetParam()) {} |
| ~WebRtcScreenCaptureBrowserTestUserRejection() override = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| WebRtcScreenCaptureBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(prefer_current_tab_ |
| ? switches::kThisTabCaptureAutoReject |
| : switches::kCaptureAutoReject); |
| } |
| |
| bool PreferCurrentTab() const override { return prefer_current_tab_; } |
| |
| private: |
| const bool prefer_current_tab_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(, WebRtcScreenCaptureBrowserTestUserRejection, Bool()); |
| |
| IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestUserRejection, |
| CorrectErrorReported) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| OpenTestPageInNewTab(kCapturedPageMain); |
| content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage); |
| |
| RunGetDisplayMedia( |
| capturing_tab, |
| GetConstraints( |
| /*video=*/true, /*audio=*/false), |
| /*is_fake_ui=*/false, |
| /*expect_success=*/false, |
| /*is_tab_capture=*/true, |
| /*expected_error=*/"NotAllowedError: Permission denied by user"); |
| } |
| |
| // RestrictOwnAudio is only supported on macOS and Windows. |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |
| class GetDisplayMediaRestrictOwnAudioTest |
| : public WebRtcTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| GetDisplayMediaRestrictOwnAudioTest() : test_config_(GetParam()) {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kAutoSelectScreenCaptureSource); |
| command_line->AppendSwitch(switches::kSystemAudioCaptureDefaultChecked); |
| } |
| |
| void SetUpOnMainThread() override { |
| WebRtcTestBase::SetUpOnMainThread(); |
| // Ensure that the bot has audio devices since we know that the test will |
| // fail without it. getDisplayMedia() hangs on bots without audio devices |
| // unless fake audio is used. A real audio track is required to verify that |
| // both the device ID and track label are correct; hence using a fake audio |
| // device will not work for this test. |
| if (!HasAudioOutputDevices() || !HasAudioInputDevices()) { |
| GTEST_SKIP() << "Missing audio devices: skipping test..."; |
| } |
| #if BUILDFLAG(IS_MAC) |
| // The API for system audio sharing is available from macOS 14.2. If macOS |
| // older then 14.2 is used, there will be no option in the UI for sharing |
| // system audio, and we will not have an audio track in the test. |
| int mac_os_version = base::mac::MacOSVersion(); |
| int major_mac_os_version = mac_os_version / 1'00'00; |
| int minor_mac_os_version = (mac_os_version / 1'00) % 1'00; |
| if (major_mac_os_version <= 13 || |
| (major_mac_os_version == 14 && minor_mac_os_version < 2)) { |
| GTEST_SKIP() << "macOS version do not support system audio sharing: " |
| "skipping test..."; |
| } |
| #endif |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| #if BUILDFLAG(IS_MAC) |
| feature_list_.InitWithFeatures({media::kMacCatapLoopbackAudioForCast, |
| media::kMacCatapLoopbackAudioForScreenShare, |
| blink::features::kRestrictOwnAudio}, |
| {media::kUseSCContentSharingPicker}); |
| #elif BUILDFLAG(IS_WIN) |
| feature_list_.InitWithFeatures({blink::features::kRestrictOwnAudio}, {}); |
| #endif |
| |
| WebRtcTestBase::SetUpInProcessBrowserTestFixture(); |
| |
| DetectErrorsInJavaScript(); |
| } |
| |
| // Synchronously checks if the system/bot has audio output devices. |
| bool HasAudioOutputDevices() { |
| bool has_devices = false; |
| base::RunLoop run_loop; |
| auto audio_system = content::CreateAudioSystemForAudioService(); |
| audio_system->HasOutputDevices(base::BindOnce( |
| [](base::OnceClosure finished_callback, bool* result, bool received) { |
| *result = received; |
| std::move(finished_callback).Run(); |
| }, |
| run_loop.QuitClosure(), &has_devices)); |
| run_loop.Run(); |
| return has_devices; |
| } |
| // Synchronously checks if the system/bot has audio input devices. |
| bool HasAudioInputDevices() { |
| bool has_devices = false; |
| base::RunLoop run_loop; |
| auto audio_system = content::CreateAudioSystemForAudioService(); |
| audio_system->HasInputDevices(base::BindOnce( |
| [](base::OnceClosure finished_callback, bool* result, bool received) { |
| *result = received; |
| std::move(finished_callback).Run(); |
| }, |
| run_loop.QuitClosure(), &has_devices)); |
| run_loop.Run(); |
| return has_devices; |
| } |
| |
| std::string GetConstraintsSystemAudio(bool suppress_local_audio_playback, |
| bool restrict_own_audio) const { |
| return base::StringPrintf( |
| "{video: true, audio: {suppressLocalAudioPlayback: %s, " |
| "restrictOwnAudio: %s}, preferCurrentTab: false, systemAudio: " |
| "\"include\"}", |
| base::ToString(suppress_local_audio_playback), |
| base::ToString(restrict_own_audio)); |
| } |
| |
| const TestConfigForRestrictOwnAudio test_config_; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| GetDisplayMediaRestrictOwnAudioTest, |
| Combine( |
| /*restrict_own_audio=*/Bool(), |
| /*suppress_local_audio_playback=*/Bool())); |
| |
| IN_PROC_BROWSER_TEST_P(GetDisplayMediaRestrictOwnAudioTest, |
| ScreenCaptureWithRestrictOwnAudio) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage); |
| RunGetDisplayMedia( |
| tab, |
| GetConstraintsSystemAudio(test_config_.suppress_local_audio_playback, |
| test_config_.restrict_own_audio), |
| /*is_fake_ui=*/false, |
| /*expect_success=*/true, |
| /*is_tab_capture=*/false); |
| EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "hasAudioTrack();"), |
| "true"); |
| EXPECT_EQ( |
| content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioTrackLabel();"), |
| "System Audio"); |
| |
| if (test_config_.restrict_own_audio) { |
| EXPECT_EQ( |
| content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioDeviceId();"), |
| "loopbackWithoutChrome"); |
| } else if (test_config_.suppress_local_audio_playback) { |
| EXPECT_EQ( |
| content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioDeviceId();"), |
| "loopbackWithMute"); |
| } else { |
| EXPECT_EQ( |
| content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioDeviceId();"), |
| "loopback"); |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |