| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "weblayer/app/content_main_delegate_impl.h" |
| |
| #include <iostream> |
| #include <tuple> |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/cpu.h" |
| #include "base/files/file_util.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_split.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/content_capture/common/content_capture_features.h" |
| #include "components/startup_metric_utils/browser/startup_metric_utils.h" |
| #include "components/translate/core/common/translate_util.h" |
| #include "components/variations/variations_ids_provider.h" |
| #include "content/public/browser/browser_main_runner.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/main_function_params.h" |
| #include "content/public/common/url_constants.h" |
| #include "media/base/media_switches.h" |
| #include "services/network/public/cpp/features.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/platform/web_runtime_features.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/ui_base_paths.h" |
| #include "weblayer/browser/background_fetch/background_fetch_delegate_factory.h" |
| #include "weblayer/browser/content_browser_client_impl.h" |
| #include "weblayer/common/content_client_impl.h" |
| #include "weblayer/common/weblayer_paths.h" |
| #include "weblayer/public/common/switches.h" |
| #include "weblayer/renderer/content_renderer_client_impl.h" |
| #include "weblayer/utility/content_utility_client_impl.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "base/android/apk_assets.h" |
| #include "base/android/build_info.h" |
| #include "base/android/bundle_utils.h" |
| #include "base/android/java_exception_reporter.h" |
| #include "base/android/locale_utils.h" |
| #include "base/i18n/rtl.h" |
| #include "base/posix/global_descriptors.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/viz/common/features.h" |
| #include "content/public/browser/android/compositor.h" |
| #include "ui/base/resource/resource_bundle_android.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "weblayer/browser/android/application_info_helper.h" |
| #include "weblayer/browser/android/exception_filter.h" |
| #include "weblayer/browser/android_descriptors.h" |
| #include "weblayer/common/crash_reporter/crash_keys.h" |
| #include "weblayer/common/crash_reporter/crash_reporter_client.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include <windows.h> |
| |
| #include <initguid.h> |
| #include "base/logging_win.h" |
| #endif |
| |
| namespace weblayer { |
| |
| namespace { |
| |
| void InitLogging(MainParams* params) { |
| if (params->log_filename.empty()) |
| return; |
| |
| logging::LoggingSettings settings; |
| settings.logging_dest = logging::LOG_TO_ALL; |
| settings.log_file_path = params->log_filename.value().c_str(); |
| settings.delete_old = logging::DELETE_OLD_LOG_FILE; |
| logging::InitLogging(settings); |
| logging::SetLogItems(true /* Process ID */, true /* Thread ID */, |
| true /* Timestamp */, false /* Tick count */); |
| } |
| |
| // Enables each feature in |features_to_enable| unless it is already set in the |
| // command line, and similarly disables each feature in |features_to_disable| |
| // unless it is already set in the command line. |
| void ConfigureFeaturesIfNotSet( |
| const std::vector<base::Feature>& features_to_enable, |
| const std::vector<base::Feature>& features_to_disable) { |
| auto* cl = base::CommandLine::ForCurrentProcess(); |
| std::vector<std::string> enabled_features; |
| base::flat_set<std::string> feature_names_enabled_via_command_line; |
| std::string enabled_features_str = |
| cl->GetSwitchValueASCII(::switches::kEnableFeatures); |
| for (const auto& f : |
| base::FeatureList::SplitFeatureListString(enabled_features_str)) { |
| enabled_features.emplace_back(f); |
| |
| // "<" is used as separator for field trial/groups. |
| std::vector<base::StringPiece> parts = base::SplitStringPiece( |
| f, "<", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| // Split with supplied params should always return at least one entry. |
| DCHECK(!parts.empty()); |
| if (parts[0].length() > 0) |
| feature_names_enabled_via_command_line.insert(std::string(parts[0])); |
| } |
| |
| std::vector<std::string> disabled_features; |
| std::string disabled_features_str = |
| cl->GetSwitchValueASCII(::switches::kDisableFeatures); |
| for (const auto& f : |
| base::FeatureList::SplitFeatureListString(disabled_features_str)) { |
| disabled_features.emplace_back(f); |
| } |
| |
| for (const auto& feature : features_to_enable) { |
| if (!base::Contains(disabled_features, feature.name) && |
| !base::Contains(feature_names_enabled_via_command_line, feature.name)) { |
| enabled_features.push_back(feature.name); |
| } |
| } |
| cl->AppendSwitchASCII(::switches::kEnableFeatures, |
| base::JoinString(enabled_features, ",")); |
| |
| for (const auto& feature : features_to_disable) { |
| if (!base::Contains(disabled_features, feature.name) && |
| !base::Contains(feature_names_enabled_via_command_line, feature.name)) { |
| disabled_features.push_back(feature.name); |
| } |
| } |
| cl->AppendSwitchASCII(::switches::kDisableFeatures, |
| base::JoinString(disabled_features, ",")); |
| } |
| |
| } // namespace |
| |
| ContentMainDelegateImpl::ContentMainDelegateImpl(MainParams params) |
| : params_(std::move(params)) { |
| #if !BUILDFLAG(IS_ANDROID) |
| // On non-Android, the application start time is recorded in this constructor, |
| // which runs early during application lifetime. On Android, the application |
| // start time is sampled when the Java code is entered, and it is retrieved |
| // from C++ after initializing the JNI (see |
| // BrowserMainPartsImpl::PreMainMessageLoopRun()). |
| startup_metric_utils::RecordApplicationStartTime(base::TimeTicks::Now()); |
| #endif |
| } |
| |
| ContentMainDelegateImpl::~ContentMainDelegateImpl() = default; |
| |
| bool ContentMainDelegateImpl::BasicStartupComplete(int* exit_code) { |
| int dummy; |
| if (!exit_code) |
| exit_code = &dummy; |
| |
| // Disable features which are not currently supported in WebLayer. This allows |
| // sites to do feature detection, and prevents crashes in some not fully |
| // implemented features. |
| base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); |
| // TODO(crbug.com/1025610): make notifications work with WebLayer. |
| // This also turns off Push messaging. |
| cl->AppendSwitch(::switches::kDisableNotifications); |
| |
| std::vector<base::Feature> enabled_features = { |
| #if BUILDFLAG(IS_ANDROID) |
| // Overlay promotion requires some guarantees we don't have on WebLayer |
| // (e.g. ensuring fullscreen, no movement of the parent view). Given that |
| // we're unsure about the benefits when used embedded in a parent app, we |
| // will only promote to overlays if needed for secure videos. |
| media::kUseAndroidOverlayForSecureOnly, |
| #endif |
| }; |
| |
| std::vector<base::Feature> disabled_features = { |
| // TODO(crbug.com/1313771): Support Digital Goods API. |
| ::features::kDigitalGoodsApi, |
| // TODO(crbug.com/1091212): make Notification triggers work with |
| // WebLayer. |
| ::features::kNotificationTriggers, |
| // TODO(crbug.com/1091211): Support PeriodicBackgroundSync on WebLayer. |
| ::features::kPeriodicBackgroundSync, |
| // TODO(crbug.com/1174856): Support Portals. |
| blink::features::kPortals, |
| // TODO(crbug.com/1144912): Support BackForwardCache on WebLayer. |
| ::features::kBackForwardCache, |
| // TODO(crbug.com/1247836): Enable TFLite/Optimization Guide on WebLayer. |
| translate::kTFLiteLanguageDetectionEnabled, |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // TODO(crbug.com/1131016): Support Picture in Picture API on WebLayer. |
| media::kPictureInPictureAPI, |
| |
| ::features::kDisableDeJelly, |
| ::features::kDynamicColorGamut, |
| #else |
| // WebOTP is supported only on Android in WebLayer. |
| ::features::kWebOTP, |
| #endif |
| }; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| if (base::android::BuildInfo::GetInstance()->sdk_int() >= |
| base::android::SDK_VERSION_OREO) { |
| enabled_features.push_back( |
| autofill::features::kAutofillExtractAllDatalists); |
| enabled_features.push_back( |
| autofill::features::kAutofillSkipComparingInferredLabels); |
| } |
| |
| if (GetApplicationMetadataAsBoolean( |
| "org.chromium.weblayer.ENABLE_LOGGING_OF_JS_CONSOLE_MESSAGES", |
| /*default_value=*/false)) { |
| enabled_features.push_back(features::kLogJsConsoleMessages); |
| } |
| #endif |
| |
| ConfigureFeaturesIfNotSet(enabled_features, disabled_features); |
| |
| // TODO(crbug.com/1097105): Support Web GPU on WebLayer. |
| blink::WebRuntimeFeatures::EnableWebGPU(false); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| content::Compositor::Initialize(); |
| #endif |
| |
| InitLogging(¶ms_); |
| |
| RegisterPathProvider(); |
| |
| return false; |
| } |
| |
| bool ContentMainDelegateImpl::ShouldCreateFeatureList() { |
| #if BUILDFLAG(IS_ANDROID) |
| // On android WebLayer is in charge of creating its own FeatureList. |
| return false; |
| #else |
| // TODO(weblayer-dev): Support feature lists on desktop. |
| return true; |
| #endif |
| } |
| |
| variations::VariationsIdsProvider* |
| ContentMainDelegateImpl::CreateVariationsIdsProvider() { |
| // As the embedder supplies the set of ids, the signed-in state does not make |
| // sense and is ignored. |
| return variations::VariationsIdsProvider::Create( |
| variations::VariationsIdsProvider::Mode::kIgnoreSignedInState); |
| } |
| |
| void ContentMainDelegateImpl::PreSandboxStartup() { |
| // TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is |
| // complete. |
| #if defined(ARCH_CPU_ARM_FAMILY) && \ |
| (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || \ |
| BUILDFLAG(IS_CHROMEOS_LACROS)) |
| // Create an instance of the CPU class to parse /proc/cpuinfo and cache |
| // cpu_brand info. |
| base::CPU cpu_info; |
| #endif |
| |
| const base::CommandLine& command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| const bool is_browser_process = |
| command_line.GetSwitchValueASCII(::switches::kProcessType).empty(); |
| if (is_browser_process && |
| command_line.HasSwitch(switches::kWebLayerUserDataDir)) { |
| base::FilePath path = |
| command_line.GetSwitchValuePath(switches::kWebLayerUserDataDir); |
| if (base::DirectoryExists(path) || base::CreateDirectory(path)) { |
| // Profile needs an absolute path, which we would normally get via |
| // PathService. In this case, manually ensure the path is absolute. |
| if (!path.IsAbsolute()) |
| path = base::MakeAbsoluteFilePath(path); |
| } else { |
| LOG(ERROR) << "Unable to create data-path directory: " << path.value(); |
| } |
| CHECK(base::PathService::OverrideAndCreateIfNeeded( |
| DIR_USER_DATA, path, true /* is_absolute */, false /* create */)); |
| } |
| |
| InitializeResourceBundle(); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| EnableCrashReporter( |
| command_line.GetSwitchValueASCII(::switches::kProcessType)); |
| if (is_browser_process) { |
| base::android::SetJavaExceptionFilter( |
| base::BindRepeating(&WebLayerJavaExceptionFilter)); |
| } |
| SetWebLayerCrashKeys(); |
| #endif |
| } |
| |
| void ContentMainDelegateImpl::PostEarlyInitialization(bool is_running_tests) { |
| browser_client_->CreateFeatureListAndFieldTrials(); |
| } |
| |
| absl::variant<int, content::MainFunctionParams> |
| ContentMainDelegateImpl::RunProcess( |
| const std::string& process_type, |
| content::MainFunctionParams main_function_params) { |
| // For non-browser process, return and have the caller run the main loop. |
| if (!process_type.empty()) |
| return std::move(main_function_params); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // On non-Android, we can return |main_function_params| back and have the |
| // caller run BrowserMain() normally. |
| return std::move(main_function_params); |
| #else |
| // On Android, we defer to the system message loop when the stack unwinds. |
| // So here we only create (and leak) a BrowserMainRunner. The shutdown |
| // of BrowserMainRunner doesn't happen in Chrome Android and doesn't work |
| // properly on Android at all. |
| auto main_runner = content::BrowserMainRunner::Create(); |
| // In browser tests, the |main_function_params| contains a |ui_task| which |
| // will execute the testing. The task will be executed synchronously inside |
| // Initialize() so we don't depend on the BrowserMainRunner being Run(). |
| int initialize_exit_code = |
| main_runner->Initialize(std::move(main_function_params)); |
| DCHECK_LT(initialize_exit_code, 0) |
| << "BrowserMainRunner::Initialize failed in MainDelegate"; |
| std::ignore = main_runner.release(); |
| // Return 0 as BrowserMain() should not be called after this, bounce up to |
| // the system message loop for ContentShell, and we're already done thanks |
| // to the |ui_task| for browser tests. |
| return 0; |
| #endif |
| } |
| |
| void ContentMainDelegateImpl::InitializeResourceBundle() { |
| #if BUILDFLAG(IS_ANDROID) |
| const base::CommandLine& command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| |
| bool is_browser_process = |
| command_line.GetSwitchValueASCII(::switches::kProcessType).empty(); |
| if (is_browser_process) { |
| // If we're not being loaded from a bundle, locales will be loaded from the |
| // webview stored-locales directory. Otherwise, we are in Monochrome, and |
| // we load both chrome and webview's locale assets. |
| if (base::android::BundleUtils::IsBundle()) |
| ui::SetLoadSecondaryLocalePaks(true); |
| else |
| ui::SetLocalePaksStoredInApk(true); |
| // Passing an empty |pref_locale| yields the system default locale. |
| std::string locale = ui::ResourceBundle::InitSharedInstanceWithLocale( |
| {} /*pref_locale*/, nullptr, ui::ResourceBundle::LOAD_COMMON_RESOURCES); |
| |
| if (locale.empty()) { |
| LOG(WARNING) << "Failed to load locale .pak from apk."; |
| } |
| |
| // Try to directly mmap the resources.pak from the apk. Fall back to load |
| // from file, using PATH_SERVICE, otherwise. |
| base::FilePath pak_file_path; |
| base::PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &pak_file_path); |
| pak_file_path = pak_file_path.AppendASCII("resources.pak"); |
| ui::LoadMainAndroidPackFile("assets/resources.pak", pak_file_path); |
| |
| // The English-only workaround is not needed for bundles, since bundles will |
| // contain assets for all locales. |
| if (!base::android::BundleUtils::IsBundle()) { |
| constexpr char kWebLayerLocalePath[] = |
| "assets/stored-locales/weblayer/en-US.pak"; |
| base::MemoryMappedFile::Region region; |
| int fd = base::android::OpenApkAsset(kWebLayerLocalePath, ®ion); |
| CHECK_GE(fd, 0) << "Could not find " << kWebLayerLocalePath << " in APK."; |
| ui::ResourceBundle::GetSharedInstance() |
| .LoadSecondaryLocaleDataWithPakFileRegion(base::File(fd), region); |
| base::GlobalDescriptors::GetInstance()->Set( |
| kWebLayerSecondaryLocalePakDescriptor, fd, region); |
| } |
| } else { |
| base::i18n::SetICUDefaultLocale( |
| command_line.GetSwitchValueASCII(::switches::kLang)); |
| |
| auto* global_descriptors = base::GlobalDescriptors::GetInstance(); |
| int pak_fd = global_descriptors->Get(kWebLayerLocalePakDescriptor); |
| base::MemoryMappedFile::Region pak_region = |
| global_descriptors->GetRegion(kWebLayerLocalePakDescriptor); |
| ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(base::File(pak_fd), |
| pak_region); |
| |
| pak_fd = global_descriptors->Get(kWebLayerSecondaryLocalePakDescriptor); |
| pak_region = |
| global_descriptors->GetRegion(kWebLayerSecondaryLocalePakDescriptor); |
| ui::ResourceBundle::GetSharedInstance() |
| .LoadSecondaryLocaleDataWithPakFileRegion(base::File(pak_fd), |
| pak_region); |
| |
| std::vector<std::pair<int, ui::ResourceScaleFactor>> extra_paks = { |
| {kWebLayerMainPakDescriptor, ui::kScaleFactorNone}, |
| {kWebLayer100PercentPakDescriptor, ui::k100Percent}}; |
| |
| for (const auto& pak_info : extra_paks) { |
| pak_fd = global_descriptors->Get(pak_info.first); |
| pak_region = global_descriptors->GetRegion(pak_info.first); |
| ui::ResourceBundle::GetSharedInstance().AddDataPackFromFileRegion( |
| base::File(pak_fd), pak_region, pak_info.second); |
| } |
| } |
| #else |
| base::FilePath pak_file; |
| bool r = base::PathService::Get(base::DIR_ASSETS, &pak_file); |
| DCHECK(r); |
| pak_file = pak_file.AppendASCII(params_.pak_name); |
| ui::ResourceBundle::InitSharedInstanceWithPakPath(pak_file); |
| #endif |
| } |
| |
| content::ContentClient* ContentMainDelegateImpl::CreateContentClient() { |
| content_client_ = std::make_unique<ContentClientImpl>(); |
| return content_client_.get(); |
| } |
| |
| content::ContentBrowserClient* |
| ContentMainDelegateImpl::CreateContentBrowserClient() { |
| browser_client_ = std::make_unique<ContentBrowserClientImpl>(¶ms_); |
| return browser_client_.get(); |
| } |
| |
| content::ContentRendererClient* |
| ContentMainDelegateImpl::CreateContentRendererClient() { |
| renderer_client_ = std::make_unique<ContentRendererClientImpl>(); |
| return renderer_client_.get(); |
| } |
| |
| content::ContentUtilityClient* |
| ContentMainDelegateImpl::CreateContentUtilityClient() { |
| utility_client_ = std::make_unique<ContentUtilityClientImpl>(); |
| return utility_client_.get(); |
| } |
| |
| } // namespace weblayer |