// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/webui/ui_bundled/flags_ui.h"
#import <memory>
#import <string>
#import <utility>
#import "base/functional/bind.h"
#import "base/functional/callback_helpers.h"
#import "base/memory/ptr_util.h"
#import "base/strings/utf_string_conversions.h"
#import "base/values.h"
#import "build/branding_buildflags.h"
#import "components/flags_ui/flags_ui_constants.h"
#import "components/flags_ui/flags_ui_pref_names.h"
#import "components/flags_ui/pref_service_flags_storage.h"
#import "components/grit/flags_ui_resources.h"
#import "components/grit/flags_ui_resources_map.h"
#import "components/prefs/pref_registry_simple.h"
#import "components/prefs/pref_service.h"
#import "components/strings/grit/components_branded_strings.h"
#import "components/strings/grit/components_strings.h"
#import "components/version_info/version_info.h"
#import "ios/chrome/browser/flags/about_flags.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/web/public/webui/web_ui_ios.h"
#import "ios/web/public/webui/web_ui_ios_data_source.h"
#import "ios/web/public/webui/web_ui_ios_message_handler.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/resource/resource_bundle.h"
namespace {
web::WebUIIOSDataSource* CreateFlagsUIHTMLSource() {
web::WebUIIOSDataSource* source =
web::WebUIIOSDataSource::Create(kChromeUIFlagsHost);
source->AddString(flags_ui::kVersion,
std::string(version_info::GetVersionNumber()));
source->UseStringsJs();
FlagsUI::AddFlagsIOSStrings(source);
source->AddResourcePaths(
base::make_span(kFlagsUiResources, kFlagsUiResourcesSize));
source->SetDefaultResource(IDR_FLAGS_UI_FLAGS_HTML);
source->UseStringsJs();
source->EnableReplaceI18nInJS();
return source;
}
////////////////////////////////////////////////////////////////////////////////
//
// FlagsDOMHandler
//
////////////////////////////////////////////////////////////////////////////////
// The handler for Javascript messages for the about:flags page.
class FlagsDOMHandler : public web::WebUIIOSMessageHandler {
public:
FlagsDOMHandler() : access_(flags_ui::kGeneralAccessFlagsOnly) {}
FlagsDOMHandler(const FlagsDOMHandler&) = delete;
FlagsDOMHandler& operator=(const FlagsDOMHandler&) = delete;
~FlagsDOMHandler() override {}
// Initializes the DOM handler with the provided flags storage and flags
// access. If there were flags experiments requested from javascript before
// this was called, it calls `HandleRequestExperimentalFeatures` again.
void Init(std::unique_ptr<flags_ui::FlagsStorage> flags_storage,
flags_ui::FlagAccess access);
// WebUIMessageHandler implementation.
void RegisterMessages() override;
// Callback for the "requestExperimentFeatures" message.
void HandleRequestExperimentalFeatures(const base::Value::List& args);
// Callback for the "enableExperimentalFeature" message.
void HandleEnableExperimentalFeatureMessage(const base::Value::List& args);
// Callback for the "restartBrowser" message. Restores all tabs on restart.
void HandleRestartBrowser(const base::Value::List& args);
// Callback for the "resetAllFlags" message.
void HandleResetAllFlags(const base::Value::List& args);
private:
std::unique_ptr<flags_ui::FlagsStorage> flags_storage_;
flags_ui::FlagAccess access_;
};
void FlagsDOMHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
flags_ui::kRequestExperimentalFeatures,
base::BindRepeating(&FlagsDOMHandler::HandleRequestExperimentalFeatures,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
flags_ui::kEnableExperimentalFeature,
base::BindRepeating(
&FlagsDOMHandler::HandleEnableExperimentalFeatureMessage,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
flags_ui::kRestartBrowser,
base::BindRepeating(&FlagsDOMHandler::HandleRestartBrowser,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
flags_ui::kResetAllFlags,
base::BindRepeating(&FlagsDOMHandler::HandleResetAllFlags,
base::Unretained(this)));
}
void FlagsDOMHandler::Init(
std::unique_ptr<flags_ui::FlagsStorage> flags_storage,
flags_ui::FlagAccess access) {
flags_storage_ = std::move(flags_storage);
access_ = access;
}
void FlagsDOMHandler::HandleRequestExperimentalFeatures(
const base::Value::List& args) {
DCHECK(flags_storage_);
DCHECK(!args.empty());
const base::Value& callback_id = args[0];
base::Value::List supported_features;
base::Value::List unsupported_features;
GetFlagFeatureEntries(flags_storage_.get(), access_, supported_features,
unsupported_features);
base::Value::Dict results;
results.Set(flags_ui::kSupportedFeatures, std::move(supported_features));
results.Set(flags_ui::kUnsupportedFeatures, std::move(unsupported_features));
results.Set(flags_ui::kNeedsRestart, IsRestartNeededToCommitChanges());
results.Set(flags_ui::kShowOwnerWarning,
access_ == flags_ui::kGeneralAccessFlagsOnly);
results.Set(flags_ui::kShowBetaChannelPromotion, false);
results.Set(flags_ui::kShowDevChannelPromotion, false);
web_ui()->ResolveJavascriptCallback(callback_id, results);
}
void FlagsDOMHandler::HandleEnableExperimentalFeatureMessage(
const base::Value::List& args) {
DCHECK(flags_storage_);
DCHECK_EQ(2u, args.size());
if (args.size() != 2)
return;
const std::string* entry_internal_name = args[0].GetIfString();
const std::string* enable_str = args[1].GetIfString();
if (!entry_internal_name || !enable_str)
return;
SetFeatureEntryEnabled(flags_storage_.get(), *entry_internal_name,
*enable_str == "true");
flags_storage_->CommitPendingWrites();
}
void FlagsDOMHandler::HandleRestartBrowser(const base::Value::List& args) {
#if BUILDFLAG(CHROMIUM_BRANDING)
CHECK(false);
#endif // BUILDFLAG(CHROMIUM_BRANDING)
}
void FlagsDOMHandler::HandleResetAllFlags(const base::Value::List& args) {
DCHECK(flags_storage_);
ResetAllFlags(flags_storage_.get());
flags_storage_->CommitPendingWrites();
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
//
// FlagsUI
//
///////////////////////////////////////////////////////////////////////////////
FlagsUI::FlagsUI(web::WebUIIOS* web_ui, const std::string& host)
: web::WebUIIOSController(web_ui, host), weak_factory_(this) {
FlagsDOMHandler* handler = new FlagsDOMHandler();
web_ui->AddMessageHandler(base::WrapUnique(handler));
flags_ui::FlagAccess flag_access = flags_ui::kOwnerAccessToFlags;
handler->Init(std::make_unique<flags_ui::PrefServiceFlagsStorage>(
GetApplicationContext()->GetLocalState()),
flag_access);
// Set up the about:flags source.
web::WebUIIOSDataSource::Add(ChromeBrowserState::FromWebUIIOS(web_ui),
CreateFlagsUIHTMLSource());
}
FlagsUI::~FlagsUI() {}
// static
void FlagsUI::AddFlagsIOSStrings(web::WebUIIOSDataSource* source) {
// Strings added here are all marked a non-translatable, so they are not
// actually localized.
source->AddLocalizedString(flags_ui::kFlagsRestartNotice,
IDS_FLAGS_UI_RELAUNCH_NOTICE);
source->AddLocalizedString("available", IDS_FLAGS_UI_AVAILABLE_FEATURE);
source->AddLocalizedString("clear-search", IDS_FLAGS_UI_CLEAR_SEARCH);
source->AddLocalizedString("disabled", IDS_FLAGS_UI_DISABLED_FEATURE);
source->AddLocalizedString("enabled", IDS_FLAGS_UI_ENABLED_FEATURE);
source->AddLocalizedString("experiment-enabled",
IDS_FLAGS_UI_EXPERIMENT_ENABLED);
source->AddLocalizedString("heading", IDS_FLAGS_UI_TITLE);
source->AddLocalizedString("no-results", IDS_FLAGS_UI_NO_RESULTS);
source->AddLocalizedString("not-available-platform",
IDS_FLAGS_UI_NOT_AVAILABLE_ON_PLATFORM);
source->AddLocalizedString("page-warning", IDS_FLAGS_UI_PAGE_WARNING);
source->AddLocalizedString("page-warning-explanation",
IDS_FLAGS_UI_PAGE_WARNING_EXPLANATION);
source->AddLocalizedString("relaunch", IDS_FLAGS_UI_RELAUNCH);
source->AddLocalizedString("reset", IDS_FLAGS_UI_PAGE_RESET);
source->AddLocalizedString("reset-acknowledged",
IDS_FLAGS_UI_RESET_ACKNOWLEDGED);
source->AddLocalizedString("search-label", IDS_FLAGS_UI_SEARCH_LABEL);
source->AddLocalizedString("search-placeholder",
IDS_FLAGS_UI_SEARCH_PLACEHOLDER);
source->AddLocalizedString("title", IDS_FLAGS_UI_TITLE);
source->AddLocalizedString("unavailable", IDS_FLAGS_UI_UNAVAILABLE_FEATURE);
source->AddLocalizedString("searchResultsSingular",
IDS_FLAGS_UI_SEARCH_RESULTS_SINGULAR);
source->AddLocalizedString("searchResultsPlural",
IDS_FLAGS_UI_SEARCH_RESULTS_PLURAL);
}