| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fuchsia_web/webinstance_host/web_instance_host_internal.h" |
| |
| #include <fuchsia/web/cpp/fidl.h> |
| |
| #include <utility> |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "build/build_config.h" |
| #include "build/chromecast_buildflags.h" |
| #include "components/fuchsia_component_support/feedback_registration.h" |
| #include "fuchsia_web/common/string_util.h" |
| #include "fuchsia_web/webengine/features.h" |
| #include "fuchsia_web/webengine/switches.h" |
| #include "gpu/command_buffer/service/gpu_switches.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "net/http/http_util.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "third_party/widevine/cdm/buildflags.h" |
| #include "ui/gfx/switches.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/ozone/public/ozone_switches.h" |
| |
| #if BUILDFLAG(ENABLE_WIDEVINE) && BUILDFLAG(ENABLE_CAST_RECEIVER) |
| #include "third_party/widevine/cdm/widevine_cdm_common.h" // nogncheck |
| #endif |
| |
| namespace { |
| |
| // Returns true if DRM is supported in current configuration. Currently we |
| // assume that it is supported on ARM64, but not on x64. |
| // |
| // TODO(crbug.com/1013412): Detect support for all features required for |
| // FuchsiaCdm. Specifically we need to verify that protected memory is supported |
| // and that mediacodec API provides hardware video decoders. |
| bool IsFuchsiaCdmSupported() { |
| #if BUILDFLAG(ENABLE_WIDEVINE) && defined(ARCH_CPU_ARM64) |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| // Appends |value| to the value of |switch_name| in the |command_line|. |
| // The switch is assumed to consist of comma-separated values. If |switch_name| |
| // is already set in |command_line| then a comma will be appended, followed by |
| // |value|, otherwise the switch will be set to |value|. |
| void AppendToSwitch(base::StringPiece switch_name, |
| base::StringPiece value, |
| base::CommandLine& command_line) { |
| if (!command_line.HasSwitch(switch_name)) { |
| command_line.AppendSwitchNative(switch_name, value); |
| return; |
| } |
| |
| std::string new_value = |
| base::StrCat({command_line.GetSwitchValueASCII(switch_name), ",", value}); |
| command_line.RemoveSwitch(switch_name); |
| command_line.AppendSwitchNative(switch_name, new_value); |
| } |
| |
| bool HandleUserAgentParams(fuchsia::web::CreateContextParams& params, |
| base::CommandLine& launch_args) { |
| if (!params.has_user_agent_product()) { |
| if (params.has_user_agent_version()) { |
| LOG(ERROR) << "Embedder version without product."; |
| return false; |
| } |
| return true; |
| } |
| |
| if (!net::HttpUtil::IsToken(params.user_agent_product())) { |
| LOG(ERROR) << "Invalid embedder product."; |
| return false; |
| } |
| |
| std::string product_and_version(params.user_agent_product()); |
| if (params.has_user_agent_version()) { |
| if (!net::HttpUtil::IsToken(params.user_agent_version())) { |
| LOG(ERROR) << "Invalid embedder version."; |
| return false; |
| } |
| base::StrAppend(&product_and_version, {"/", params.user_agent_version()}); |
| } |
| launch_args.AppendSwitchNative(switches::kUserAgentProductAndVersion, |
| std::move(product_and_version)); |
| return true; |
| } |
| |
| bool HandleKeyboardFeatureFlags(fuchsia::web::ContextFeatureFlags features, |
| base::CommandLine& launch_args) { |
| const bool enable_keyboard = |
| (features & fuchsia::web::ContextFeatureFlags::KEYBOARD) == |
| fuchsia::web::ContextFeatureFlags::KEYBOARD; |
| const bool enable_virtual_keyboard = |
| (features & fuchsia::web::ContextFeatureFlags::VIRTUAL_KEYBOARD) == |
| fuchsia::web::ContextFeatureFlags::VIRTUAL_KEYBOARD; |
| |
| if (enable_keyboard) { |
| AppendToSwitch(switches::kEnableFeatures, features::kKeyboardInput.name, |
| launch_args); |
| |
| if (enable_virtual_keyboard) { |
| AppendToSwitch(switches::kEnableFeatures, features::kVirtualKeyboard.name, |
| launch_args); |
| } |
| } else if (enable_virtual_keyboard) { |
| LOG(ERROR) << "VIRTUAL_KEYBOARD feature requires KEYBOARD."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void HandleUnsafelyTreatInsecureOriginsAsSecureParam( |
| fuchsia::web::CreateContextParams& params, |
| base::CommandLine& launch_args) { |
| if (!params.has_unsafely_treat_insecure_origins_as_secure()) { |
| return; |
| } |
| |
| const std::vector<std::string>& insecure_origins = |
| params.unsafely_treat_insecure_origins_as_secure(); |
| for (const auto& origin : insecure_origins) { |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| static constexpr char kDisableMixedContentAutoupgradeOrigin[] = |
| "disable-mixed-content-autoupgrade"; |
| |
| if (origin == switches::kAllowRunningInsecureContent) { |
| launch_args.AppendSwitch(switches::kAllowRunningInsecureContent); |
| continue; |
| } |
| if (origin == kDisableMixedContentAutoupgradeOrigin) { |
| // Constant from //third_party/blink/common/features.cc: |
| static constexpr char kMixedContentAutoupgradeFeatureName[] = |
| "AutoupgradeMixedContent"; |
| AppendToSwitch(switches::kDisableFeatures, |
| kMixedContentAutoupgradeFeatureName, launch_args); |
| continue; |
| } |
| #endif |
| |
| // Pass the list to the Context process. |
| AppendToSwitch(network::switches::kUnsafelyTreatInsecureOriginAsSecure, |
| origin, launch_args); |
| } |
| } |
| |
| void HandleCorsExemptHeadersParam(fuchsia::web::CreateContextParams& params, |
| base::CommandLine& launch_args) { |
| if (!params.has_cors_exempt_headers()) { |
| return; |
| } |
| |
| std::vector<base::StringPiece> cors_exempt_headers; |
| cors_exempt_headers.reserve(params.cors_exempt_headers().size()); |
| for (const auto& header : params.cors_exempt_headers()) { |
| cors_exempt_headers.push_back(BytesAsString(header)); |
| } |
| |
| launch_args.AppendSwitchNative(switches::kCorsExemptHeaders, |
| base::JoinString(cors_exempt_headers, ",")); |
| } |
| |
| void HandleDisableCodeGenerationParam( |
| fuchsia::web::ContextFeatureFlags features, |
| base::CommandLine& launch_args) { |
| if ((features & |
| fuchsia::web::ContextFeatureFlags::DISABLE_DYNAMIC_CODE_GENERATION) != |
| fuchsia::web::ContextFeatureFlags::DISABLE_DYNAMIC_CODE_GENERATION) { |
| return; |
| } |
| |
| // These flag constants must match the values defined in Blink and V8, |
| // respectively. They are duplicated here rather than creating dependencies |
| // of `WebInstanceHost` uses on those sub-projects. |
| static constexpr char kJavaScriptFlags[] = "js-flags"; |
| static constexpr char kV8JitlessFlag[] = "--jitless"; |
| |
| // Add the JIT-less option to the comma-separated set of V8 flags passed to |
| // Blink. |
| AppendToSwitch(kJavaScriptFlags, kV8JitlessFlag, launch_args); |
| |
| // TODO(crbug.com/1290907): Disable use of VmexResource in this case, once |
| // migrated off of ambient VMEX. |
| } |
| |
| } // namespace |
| |
| void RegisterWebInstanceProductData(base::StringPiece absolute_component_url) { |
| // LINT.IfChange(web_engine_crash_product_name) |
| static constexpr char kCrashProductName[] = "FuchsiaWebEngine"; |
| // LINT.ThenChange(//fuchsia_web/webengine/context_provider_main.cc:web_engine_crash_product_name) |
| |
| static constexpr char kFeedbackAnnotationsNamespace[] = "web-engine"; |
| |
| fuchsia_component_support::RegisterProductDataForCrashReporting( |
| absolute_component_url, kCrashProductName); |
| |
| fuchsia_component_support::RegisterProductDataForFeedback( |
| kFeedbackAnnotationsNamespace); |
| } |
| |
| bool IsValidContentDirectoryName(base::StringPiece file_name) { |
| if (file_name.find_first_of(base::FilePath::kSeparators, 0, |
| base::FilePath::kSeparatorsLength - 1) != |
| base::StringPiece::npos) { |
| return false; |
| } |
| if (file_name == base::FilePath::kCurrentDirectory || |
| file_name == base::FilePath::kParentDirectory) { |
| return false; |
| } |
| return true; |
| } |
| |
| zx_status_t AppendLaunchArgs(fuchsia::web::CreateContextParams& params, |
| base::CommandLine& launch_args) { |
| // Arguments to be stripped rather than propagated. |
| launch_args.RemoveSwitch(switches::kContextProvider); |
| |
| const fuchsia::web::ContextFeatureFlags features = |
| params.has_features() ? params.features() |
| : fuchsia::web::ContextFeatureFlags(); |
| |
| if (params.has_remote_debugging_port()) { |
| if ((features & fuchsia::web::ContextFeatureFlags::NETWORK) != |
| fuchsia::web::ContextFeatureFlags::NETWORK) { |
| LOG(ERROR) << "Enabling remote debugging port requires NETWORK feature."; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| // Constant copied from //content/public/common/content_switches.cc: |
| static constexpr char kRemoteDebuggingPort[] = "remote-debugging-port"; |
| launch_args.AppendSwitchNative( |
| kRemoteDebuggingPort, |
| base::NumberToString(params.remote_debugging_port())); |
| } |
| |
| const bool is_headless = |
| (features & fuchsia::web::ContextFeatureFlags::HEADLESS) == |
| fuchsia::web::ContextFeatureFlags::HEADLESS; |
| if (is_headless) { |
| launch_args.AppendSwitchNative(switches::kOzonePlatform, |
| switches::kHeadless); |
| launch_args.AppendSwitch(switches::kHeadless); |
| } |
| |
| if ((features & fuchsia::web::ContextFeatureFlags::LEGACYMETRICS) == |
| fuchsia::web::ContextFeatureFlags::LEGACYMETRICS) { |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| launch_args.AppendSwitch(switches::kUseLegacyMetricsService); |
| #else |
| LOG(WARNING) << "LEGACYMETRICS is not supported."; |
| #endif |
| } |
| |
| const bool enable_vulkan = |
| (features & fuchsia::web::ContextFeatureFlags::VULKAN) == |
| fuchsia::web::ContextFeatureFlags::VULKAN; |
| bool enable_widevine = |
| (features & fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM) == |
| fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM; |
| bool enable_playready = params.has_playready_key_system(); |
| |
| // Verify that the configuration is compatible with DRM, if requested. |
| if (enable_widevine || enable_playready) { |
| // VULKAN is required for DRM-protected video playback. Allow DRM to also be |
| // enabled for HEADLESS Contexts, since Vulkan is never required for audio. |
| if (!enable_vulkan && !is_headless) { |
| LOG(ERROR) << "WIDEVINE_CDM and PLAYREADY_CDM features require VULKAN " |
| " or HEADLESS."; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (!params.has_cdm_data_directory()) { |
| LOG(ERROR) << "WIDEVINE_CDM and PLAYREADY_CDM features require a " |
| "|cdm_data_directory|."; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| // |cdm_data_directory| will be handled later. |
| } |
| |
| // If the system doesn't actually support DRM then disable it. This may result |
| // in the Context being able to run without using protected buffers. |
| if (enable_playready && !IsFuchsiaCdmSupported()) { |
| LOG(WARNING) << "PlayReady is not supported on this device."; |
| enable_playready = false; |
| params.clear_playready_key_system(); |
| } |
| if (enable_widevine && !IsFuchsiaCdmSupported()) { |
| LOG(WARNING) << "Widevine is not supported on this device."; |
| enable_widevine = false; |
| } |
| |
| if (enable_vulkan) { |
| if (is_headless) { |
| DLOG(ERROR) << "VULKAN and HEADLESS features cannot be used together."; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| VLOG(1) << "Enabling Vulkan GPU acceleration."; |
| |
| // Vulkan requires use of SkiaRenderer, configured to a use Vulkan context. |
| launch_args.AppendSwitch(switches::kUseVulkan); |
| AppendToSwitch(switches::kEnableFeatures, features::kVulkan.name, |
| launch_args); |
| launch_args.AppendSwitchASCII(switches::kUseGL, |
| gl::kGLImplementationANGLEName); |
| } else { |
| VLOG(1) << "Disabling GPU acceleration."; |
| // Disable use of Vulkan GPU, and use of the software-GL rasterizer. The |
| // Context will still run a GPU process, but will not support WebGL. |
| // Constants copied from //content/public/common/content_switches.cc: |
| static constexpr char kDisableGpu[] = "disable-gpu"; |
| static constexpr char kDisableSoftwareRasterizer[] = |
| "disable-software-rasterizer"; |
| |
| launch_args.AppendSwitch(kDisableGpu); |
| launch_args.AppendSwitch(kDisableSoftwareRasterizer); |
| } |
| |
| #if BUILDFLAG(ENABLE_WIDEVINE) |
| if (enable_widevine) { |
| launch_args.AppendSwitch(switches::kEnableWidevine); |
| } |
| |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| // Use a constexpr instead of the media::IsClearKey() helper, because of the |
| // additional dependencies required. |
| static constexpr char kClearKeyKeySystem[] = "org.w3.clearkey"; |
| |
| if (enable_playready) { |
| const std::string& key_system = params.playready_key_system(); |
| if (key_system == kWidevineKeySystem || key_system == kClearKeyKeySystem) { |
| LOG(ERROR) |
| << "Invalid value for CreateContextParams/playready_key_system: " |
| << key_system; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| launch_args.AppendSwitchNative(switches::kPlayreadyKeySystem, key_system); |
| } |
| #endif // BUILDFLAG(ENABLE_CAST_RECEIVER) |
| #endif // BUILDFLAG(ENABLE_WIDEVINE) |
| |
| bool enable_audio = (features & fuchsia::web::ContextFeatureFlags::AUDIO) == |
| fuchsia::web::ContextFeatureFlags::AUDIO; |
| if (!enable_audio) { |
| // TODO(fxbug.dev/58902): Split up audio input and output in |
| // ContextFeatureFlags. |
| // Constants copied from //media/base/media_switches.cc: |
| static constexpr char kDisableAudioInput[] = "disable-audio-input"; |
| static constexpr char kDisableAudioOutput[] = "disable-audio-output"; |
| |
| launch_args.AppendSwitch(kDisableAudioOutput); |
| launch_args.AppendSwitch(kDisableAudioInput); |
| } |
| |
| bool enable_hardware_video_decoder = |
| (features & fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER) == |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER; |
| if (!enable_hardware_video_decoder) { |
| // Constant copied from //content/public/common/content_switches.cc: |
| static constexpr char kDisableAcceleratedVideoDecode[] = |
| "disable-accelerated-video-decode"; |
| launch_args.AppendSwitch(kDisableAcceleratedVideoDecode); |
| } |
| |
| if (enable_hardware_video_decoder && !enable_vulkan) { |
| DLOG(ERROR) << "HARDWARE_VIDEO_DECODER requires VULKAN."; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| bool disable_software_video_decoder = |
| (features & |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER_ONLY) == |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER_ONLY; |
| if (disable_software_video_decoder) { |
| if (!enable_hardware_video_decoder) { |
| LOG(ERROR) << "Software video decoding may only be disabled if hardware " |
| "video decoding is enabled."; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| AppendToSwitch(switches::kDisableFeatures, |
| features::kEnableSoftwareOnlyVideoCodecs.name, launch_args); |
| } |
| |
| if (!HandleUserAgentParams(params, launch_args)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (!HandleKeyboardFeatureFlags(features, launch_args)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| HandleUnsafelyTreatInsecureOriginsAsSecureParam(params, launch_args); |
| |
| HandleCorsExemptHeadersParam(params, launch_args); |
| |
| HandleDisableCodeGenerationParam(features, launch_args); |
| |
| return ZX_OK; |
| } |
| |
| void AppendDynamicServices(fuchsia::web::ContextFeatureFlags features, |
| bool enable_playready, |
| std::vector<std::string>& services) { |
| using ::fuchsia::web::ContextFeatureFlags; |
| |
| // Result of bitwise AND when no specified flag(s) are present. |
| const ContextFeatureFlags kNoFeaturesRequested = |
| static_cast<ContextFeatureFlags>(0); |
| |
| // Features are listed here in order of their enum value. |
| static constexpr struct { |
| ContextFeatureFlags flag; |
| ContextFeatureFlags value; |
| base::StringPiece service; |
| } kServices[] = { |
| {ContextFeatureFlags::NETWORK, ContextFeatureFlags::NETWORK, |
| "fuchsia.net.interfaces.State"}, |
| {ContextFeatureFlags::NETWORK, ContextFeatureFlags::NETWORK, |
| "fuchsia.net.name.Lookup"}, |
| {ContextFeatureFlags::NETWORK, ContextFeatureFlags::NETWORK, |
| "fuchsia.posix.socket.Provider"}, |
| {ContextFeatureFlags::AUDIO, ContextFeatureFlags::AUDIO, |
| "fuchsia.media.Audio"}, |
| {ContextFeatureFlags::AUDIO, ContextFeatureFlags::AUDIO, |
| "fuchsia.media.AudioDeviceEnumerator"}, |
| {ContextFeatureFlags::AUDIO, ContextFeatureFlags::AUDIO, |
| "fuchsia.media.SessionAudioConsumerFactory"}, |
| {ContextFeatureFlags::VULKAN, ContextFeatureFlags::VULKAN, |
| "fuchsia.tracing.provider.Registry"}, |
| {ContextFeatureFlags::VULKAN, ContextFeatureFlags::VULKAN, |
| "fuchsia.vulkan.loader.Loader"}, |
| {ContextFeatureFlags::HARDWARE_VIDEO_DECODER, |
| ContextFeatureFlags::HARDWARE_VIDEO_DECODER, |
| "fuchsia.mediacodec.CodecFactory"}, |
| // HARDWARE_VIDEO_DECODER_ONLY does not require any additional services. |
| #if BUILDFLAG(ENABLE_WIDEVINE) |
| {ContextFeatureFlags::WIDEVINE_CDM, ContextFeatureFlags::WIDEVINE_CDM, |
| "fuchsia.media.drm.Widevine"}, |
| #endif |
| {ContextFeatureFlags::HEADLESS, kNoFeaturesRequested, |
| "fuchsia.accessibility.semantics.SemanticsManager"}, |
| {ContextFeatureFlags::HEADLESS, kNoFeaturesRequested, |
| "fuchsia.ui.composition.Allocator"}, |
| {ContextFeatureFlags::HEADLESS, kNoFeaturesRequested, |
| "fuchsia.ui.composition.Flatland"}, |
| #if BUILDFLAG(ENABLE_CAST_RECEIVER) |
| {ContextFeatureFlags::LEGACYMETRICS, ContextFeatureFlags::LEGACYMETRICS, |
| "fuchsia.legacymetrics.MetricsRecorder"}, |
| #endif |
| {ContextFeatureFlags::KEYBOARD, ContextFeatureFlags::KEYBOARD, |
| "fuchsia.ui.input3.Keyboard"}, |
| {ContextFeatureFlags::VIRTUAL_KEYBOARD, |
| ContextFeatureFlags::VIRTUAL_KEYBOARD, |
| "fuchsia.input.virtualkeyboard.ControllerCreator"}, |
| {ContextFeatureFlags::DISABLE_DYNAMIC_CODE_GENERATION, kNoFeaturesRequested, |
| "fuchsia.kernel.VmexResource"}, |
| }; |
| for (const auto& [flag, value, service] : kServices) { |
| if ((features & flag) == value) { |
| services.push_back(std::string(service)); |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_WIDEVINE) && BUILDFLAG(ENABLE_CAST_RECEIVER) |
| if (enable_playready) { |
| services.emplace_back("fuchsia.media.drm.PlayReady"); |
| } |
| #endif |
| } |