Skip to content

Commit

Permalink
[2/3] Add ScreenCaptureWithoutGestureAllowedForOrigins policy
Browse files Browse the repository at this point in the history
The policy is a list of URL templates (applied on a per-origin basis) that allows these origins to use the getDisplayMedia() web API without the user gesture ("transient activation") requirement (pending feature launch).

This is needed for enhanced browser content redirection (BCR++) to
enable video conferencing offloading in VDI.

Google-internal design doc: https://docs.google.com/document/d/1CIybpXPScskAp7MBovXTMjeCvdrgBjAxJlNgkunutaw/edit?usp=sharing

Bug: b:271258742
Change-Id: I4a41dff5412b469ad39adee874cad45b52a87ece
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4294945
Reviewed-by: Elad Alon <eladalon@chromium.org>
Reviewed-by: Evan Liu <evliu@google.com>
Reviewed-by: Fr <beaufort.francois@gmail.com>
Reviewed-by: Maksim Ivanov <emaxx@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Jesse Doherty <jwd@chromium.org>
Commit-Queue: Alexander Hendrich <hendrich@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1118647}
  • Loading branch information
alex292 authored and Chromium LUCI CQ committed Mar 17, 2023
1 parent bd9fe27 commit 16da52a
Show file tree
Hide file tree
Showing 26 changed files with 415 additions and 53 deletions.
51 changes: 32 additions & 19 deletions chrome/browser/chrome_content_browser_client.cc
Expand Up @@ -1572,6 +1572,8 @@ void ChromeContentBrowserClient::RegisterProfilePrefs(
#if !BUILDFLAG(IS_ANDROID)
registry->RegisterBooleanPref(prefs::kAutoplayAllowed, false);
registry->RegisterListPref(prefs::kAutoplayAllowlist);
registry->RegisterListPref(
prefs::kScreenCaptureWithoutGestureAllowedForOrigins);
registry->RegisterIntegerPref(prefs::kFetchKeepaliveDurationOnShutdown, 0);
registry->RegisterBooleanPref(
prefs::kSharedArrayBufferUnrestrictedAccessAllowed, false);
Expand Down Expand Up @@ -4156,6 +4158,11 @@ void ChromeContentBrowserClient::OverrideWebkitPrefs(
}

web_prefs->autoplay_policy = GetAutoplayPolicyForWebContents(web_contents);
#if !BUILDFLAG(IS_ANDROID)
web_prefs->require_transient_activation_for_get_display_media =
capture_policy::IsTransientActivationRequiredForGetDisplayMedia(
web_contents);
#endif // !BUILDFLAG(IS_ANDROID)

switch (GetWebTheme()->GetPreferredContrast()) {
case ui::NativeTheme::PreferredContrast::kNoPreference:
Expand Down Expand Up @@ -4221,41 +4228,47 @@ bool ChromeContentBrowserClientParts::OverrideWebPreferencesAfterNavigation(

bool ChromeContentBrowserClient::OverrideWebPreferencesAfterNavigation(
WebContents* web_contents,
WebPreferences* prefs) {
WebPreferences* web_prefs) {
bool prefs_changed = false;

const auto autoplay_policy = GetAutoplayPolicyForWebContents(web_contents);
const bool new_autoplay_policy_needed =
prefs->autoplay_policy != autoplay_policy;
if (new_autoplay_policy_needed)
prefs->autoplay_policy = autoplay_policy;
prefs_changed |= (web_prefs->autoplay_policy != autoplay_policy);
web_prefs->autoplay_policy = autoplay_policy;

#if !BUILDFLAG(IS_ANDROID)
const bool require_transient_activation_for_get_display_media =
capture_policy::IsTransientActivationRequiredForGetDisplayMedia(
web_contents);
prefs_changed |=
(web_prefs->require_transient_activation_for_get_display_media !=
require_transient_activation_for_get_display_media);
web_prefs->require_transient_activation_for_get_display_media =
require_transient_activation_for_get_display_media;
#endif // !BUILDFLAG(IS_ANDROID)

bool extra_parts_need_update = false;
for (ChromeContentBrowserClientParts* parts : extra_parts_) {
extra_parts_need_update |=
parts->OverrideWebPreferencesAfterNavigation(web_contents, prefs);
prefs_changed |=
parts->OverrideWebPreferencesAfterNavigation(web_contents, web_prefs);
}

bool preferred_color_scheme_updated = UpdatePreferredColorScheme(
prefs, web_contents->GetLastCommittedURL(), web_contents, GetWebTheme());
prefs_changed |=
UpdatePreferredColorScheme(web_prefs, web_contents->GetLastCommittedURL(),
web_contents, GetWebTheme());

#if BUILDFLAG(IS_ANDROID)
bool force_dark_mode_changed = false;
auto* delegate = TabAndroid::FromWebContents(web_contents)
? static_cast<android::TabWebContentsDelegateAndroid*>(
web_contents->GetDelegate())
: nullptr;
if (delegate) {
bool force_dark_mode_new_state = delegate->IsForceDarkWebContentEnabled();
force_dark_mode_changed =
prefs->force_dark_mode_enabled != force_dark_mode_new_state;
prefs->force_dark_mode_enabled = force_dark_mode_new_state;
prefs_changed |=
(web_prefs->force_dark_mode_enabled != force_dark_mode_new_state);
web_prefs->force_dark_mode_enabled = force_dark_mode_new_state;
}
#endif

return new_autoplay_policy_needed || extra_parts_need_update ||
#if BUILDFLAG(IS_ANDROID)
force_dark_mode_changed ||
#endif
preferred_color_scheme_updated;
return prefs_changed;
}

void ChromeContentBrowserClient::BrowserURLHandlerCreated(
Expand Down
31 changes: 31 additions & 0 deletions chrome/browser/media/webrtc/capture_policy_utils.cc
Expand Up @@ -5,11 +5,13 @@
#include "chrome/browser/media/webrtc/capture_policy_utils.h"

#include "base/containers/cxx20_erase_vector.h"
#include "base/feature_list.h"
#include "base/ranges/algorithm.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
#include "chrome/browser/policy/policy_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
Expand All @@ -19,6 +21,7 @@
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/features_generated.h"
#include "url/gurl.h"
#include "url/origin.h"

Expand Down Expand Up @@ -164,6 +167,34 @@ bool IsGetDisplayMediaSetSelectAllScreensAllowed(
#endif
}

#if !BUILDFLAG(IS_ANDROID)
bool IsTransientActivationRequiredForGetDisplayMedia(
content::WebContents* contents) {
if (!base::FeatureList::IsEnabled(
blink::features::kGetDisplayMediaRequiresUserActivation)) {
return false;
}

if (!contents) {
return true;
}

Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
if (!profile) {
return true;
}

PrefService* prefs = profile->GetPrefs();
if (!prefs) {
return true;
}

return !policy::IsOriginInAllowlist(
contents->GetURL(), prefs,
prefs::kScreenCaptureWithoutGestureAllowedForOrigins);
}
#endif // !BUILDFLAG(IS_ANDROID)

DesktopMediaList::WebContentsFilter GetIncludableWebContentsFilter(
const GURL& request_origin,
AllowedScreenCaptureLevel capture_level) {
Expand Down
5 changes: 5 additions & 0 deletions chrome/browser/media/webrtc/capture_policy_utils.h
Expand Up @@ -68,6 +68,11 @@ bool IsGetDisplayMediaSetSelectAllScreensAllowed(
bool IsGetDisplayMediaSetSelectAllScreensAllowedForAnySite(
content::BrowserContext* context);

#if !BUILDFLAG(IS_ANDROID)
bool IsTransientActivationRequiredForGetDisplayMedia(
content::WebContents* contents);
#endif // !BUILDFLAG(IS_ANDROID)

} // namespace capture_policy

#endif // CHROME_BROWSER_MEDIA_WEBRTC_CAPTURE_POLICY_UTILS_H_
8 changes: 4 additions & 4 deletions chrome/browser/media/webrtc/display_media_access_handler.cc
Expand Up @@ -10,12 +10,12 @@

#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/bad_message.h"
#include "chrome/browser/media/webrtc/capture_policy_utils.h"
#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
Expand Down Expand Up @@ -197,9 +197,9 @@ void DisplayMediaAccessHandler::HandleRequest(
// before sending IPC, but just to be sure double check here as well. This
// is not treated as a BadMessage because it is possible for the transient
// user activation to expire between the renderer side check and this check.
if (base::FeatureList::IsEnabled(
blink::features::kGetDisplayMediaRequiresUserActivation) &&
!rfh->HasTransientUserActivation()) {
if (!rfh->HasTransientUserActivation() &&
capture_policy::IsTransientActivationRequiredForGetDisplayMedia(
web_contents)) {
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
Expand Down
148 changes: 139 additions & 9 deletions chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#include <string>
#include <tuple>
#include <vector>

#include "base/base_switches.h"
Expand All @@ -27,6 +28,11 @@
#include "components/infobars/content/content_infobar_manager.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "components/infobars/core/infobar.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/web_contents.h"
Expand All @@ -35,6 +41,7 @@
#include "content/public/test/browser_test_utils.h"
#include "media/base/media_switches.h"
#include "net/base/filename_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "ui/base/l10n/l10n_util.h"

Expand Down Expand Up @@ -108,6 +115,9 @@ struct TestConfigForHiDpi {

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) {
Expand All @@ -127,16 +137,23 @@ void RunGetDisplayMedia(content::WebContents* tab,
bool is_fake_ui,
bool expect_success,
bool is_tab_capture,
const std::string& expected_error = "") {
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;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
tab->GetPrimaryMainFrame(),
base::StringPrintf(
"runGetDisplayMedia(%s, \"top-level-document\", \"%s\");",
constraints.c_str(), expected_error.c_str()),
&result));

if (with_user_gesture) {
EXPECT_TRUE(
content::ExecuteScriptAndExtractString(adapter, script, &result));
} else {
EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractString(
adapter, script, &result));
}

#if BUILDFLAG(IS_MAC)
if (!is_fake_ui && !is_tab_capture &&
Expand Down Expand Up @@ -1411,8 +1428,9 @@ class WebRtcScreenCaptureSelectAllScreensTest
// Enables GetDisplayMedia and GetDisplayMediaSetAutoSelectAllScreens
// features for multi surface capture.
// TODO(simonha): remove when feature becomes stable.
if (test_config_.enable_select_all_screens)
if (test_config_.enable_select_all_screens) {
command_line->AppendSwitch(switches::kEnableBlinkTestFeatures);
}
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
Expand Down Expand Up @@ -1473,4 +1491,116 @@ INSTANTIATE_TEST_SUITE_P(
TestConfigForSelectAllScreens{/*display_surface=*/"monitor",
/*enable_select_all_screens=*/false}));

#endif
#endif // BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_CHROMEOS_ASH)

class GetDisplayMediaTransientActivationRequiredTest
: public WebRtcScreenCaptureBrowserTest,
public testing::WithParamInterface<
std::tuple<bool, bool, bool, absl::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 absl::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,
SelectAllScreens::kUndefined),
/*is_fake_ui=*/true, expect_success,
/*is_tab_capture=*/false, expected_error,
with_user_gesture_);
}

INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
GetDisplayMediaTransientActivationRequiredTest,
testing::Combine(
/*with_user_gesture=*/testing::Bool(),
/*require_gesture_feature_enabled=*/testing::Bool(),
/*prefer_current_tab=*/testing::Bool(),
/*policy_allowlist_value=*/
testing::Values(absl::nullopt,
kEmbeddedTestServerOrigin,
kOtherOrigin)),
&GetDisplayMediaTransientActivationRequiredTest::GetDescription);
Expand Up @@ -378,6 +378,9 @@ const PolicyToPreferenceMapEntry kSimplePolicyMap[] = {
{ key::kAutoplayAllowlist,
prefs::kAutoplayAllowlist,
base::Value::Type::LIST },
{ key::kScreenCaptureWithoutGestureAllowedForOrigins,
prefs::kScreenCaptureWithoutGestureAllowedForOrigins,
base::Value::Type::LIST },
{ key::kBasicAuthOverHttpEnabled,
prefs::kBasicAuthOverHttpEnabled,
base::Value::Type::BOOLEAN },
Expand Down

0 comments on commit 16da52a

Please sign in to comment.