1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/webui/flags_ui.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/memory/ref_counted_memory.h"
12#include "base/prefs/pref_registry_simple.h"
13#include "base/prefs/pref_service.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/values.h"
16#include "chrome/browser/about_flags.h"
17#include "chrome/browser/browser_process.h"
18#include "chrome/browser/lifetime/application_lifetime.h"
19#include "chrome/browser/pref_service_flags_storage.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/common/chrome_version_info.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/common/url_constants.h"
24#include "chrome/grit/chromium_strings.h"
25#include "chrome/grit/generated_resources.h"
26#include "content/public/browser/web_contents.h"
27#include "content/public/browser/web_ui.h"
28#include "content/public/browser/web_ui_data_source.h"
29#include "content/public/browser/web_ui_message_handler.h"
30#include "grit/browser_resources.h"
31#include "grit/theme_resources.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/base/resource/resource_bundle.h"
34
35#if defined(OS_CHROMEOS)
36#include "base/sys_info.h"
37#include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos.h"
38#include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
39#include "chrome/browser/chromeos/settings/cros_settings.h"
40#include "chrome/browser/chromeos/settings/owner_flags_storage.h"
41#include "chromeos/dbus/dbus_thread_manager.h"
42#include "chromeos/dbus/session_manager_client.h"
43#include "components/pref_registry/pref_registry_syncable.h"
44#include "components/user_manager/user_manager.h"
45#endif
46
47using content::WebContents;
48using content::WebUIMessageHandler;
49
50namespace {
51
52content::WebUIDataSource* CreateFlagsUIHTMLSource() {
53  content::WebUIDataSource* source =
54      content::WebUIDataSource::Create(chrome::kChromeUIFlagsHost);
55
56  source->SetUseJsonJSFormatV2();
57  source->AddLocalizedString("flagsLongTitle", IDS_FLAGS_LONG_TITLE);
58  source->AddLocalizedString("flagsTableTitle", IDS_FLAGS_TABLE_TITLE);
59  source->AddLocalizedString("flagsNoExperimentsAvailable",
60                             IDS_FLAGS_NO_EXPERIMENTS_AVAILABLE);
61  source->AddLocalizedString("flagsWarningHeader", IDS_FLAGS_WARNING_HEADER);
62  source->AddLocalizedString("flagsBlurb", IDS_FLAGS_WARNING_TEXT);
63  source->AddLocalizedString("channelPromoBeta",
64                             IDS_FLAGS_PROMOTE_BETA_CHANNEL);
65  source->AddLocalizedString("channelPromoDev", IDS_FLAGS_PROMOTE_DEV_CHANNEL);
66  source->AddLocalizedString("flagsUnsupportedTableTitle",
67                             IDS_FLAGS_UNSUPPORTED_TABLE_TITLE);
68  source->AddLocalizedString("flagsNoUnsupportedExperiments",
69                             IDS_FLAGS_NO_UNSUPPORTED_EXPERIMENTS);
70  source->AddLocalizedString("flagsNotSupported", IDS_FLAGS_NOT_AVAILABLE);
71  source->AddLocalizedString("flagsRestartNotice", IDS_FLAGS_RELAUNCH_NOTICE);
72  source->AddLocalizedString("flagsRestartButton", IDS_FLAGS_RELAUNCH_BUTTON);
73  source->AddLocalizedString("resetAllButton", IDS_FLAGS_RESET_ALL_BUTTON);
74  source->AddLocalizedString("disable", IDS_FLAGS_DISABLE);
75  source->AddLocalizedString("enable", IDS_FLAGS_ENABLE);
76
77#if defined(OS_CHROMEOS)
78  if (!user_manager::UserManager::Get()->IsCurrentUserOwner() &&
79      base::SysInfo::IsRunningOnChromeOS()) {
80    // Set the strings to show which user can actually change the flags.
81    std::string owner;
82    chromeos::CrosSettings::Get()->GetString(chromeos::kDeviceOwner, &owner);
83    source->AddString("ownerWarning",
84                      l10n_util::GetStringFUTF16(IDS_SYSTEM_FLAGS_OWNER_ONLY,
85                                                 base::UTF8ToUTF16(owner)));
86  } else {
87    // The warning will be only shown on ChromeOS, when the current user is not
88    // the owner.
89    source->AddString("ownerWarning", base::string16());
90  }
91#endif
92
93  source->SetJsonPath("strings.js");
94  source->AddResourcePath("flags.js", IDR_FLAGS_JS);
95  source->SetDefaultResource(IDR_FLAGS_HTML);
96  return source;
97}
98
99////////////////////////////////////////////////////////////////////////////////
100//
101// FlagsDOMHandler
102//
103////////////////////////////////////////////////////////////////////////////////
104
105// The handler for Javascript messages for the about:flags page.
106class FlagsDOMHandler : public WebUIMessageHandler {
107 public:
108  FlagsDOMHandler() : access_(about_flags::kGeneralAccessFlagsOnly),
109                      flags_experiments_requested_(false) {
110  }
111  virtual ~FlagsDOMHandler() {}
112
113  // Initializes the DOM handler with the provided flags storage and flags
114  // access. If there were flags experiments requested from javascript before
115  // this was called, it calls |HandleRequestFlagsExperiments| again.
116  void Init(about_flags::FlagsStorage* flags_storage,
117            about_flags::FlagAccess access);
118
119  // WebUIMessageHandler implementation.
120  virtual void RegisterMessages() OVERRIDE;
121
122  // Callback for the "requestFlagsExperiments" message.
123  void HandleRequestFlagsExperiments(const base::ListValue* args);
124
125  // Callback for the "enableFlagsExperiment" message.
126  void HandleEnableFlagsExperimentMessage(const base::ListValue* args);
127
128  // Callback for the "restartBrowser" message. Restores all tabs on restart.
129  void HandleRestartBrowser(const base::ListValue* args);
130
131  // Callback for the "resetAllFlags" message.
132  void HandleResetAllFlags(const base::ListValue* args);
133
134 private:
135  scoped_ptr<about_flags::FlagsStorage> flags_storage_;
136  about_flags::FlagAccess access_;
137  bool flags_experiments_requested_;
138
139  DISALLOW_COPY_AND_ASSIGN(FlagsDOMHandler);
140};
141
142void FlagsDOMHandler::RegisterMessages() {
143  web_ui()->RegisterMessageCallback("requestFlagsExperiments",
144      base::Bind(&FlagsDOMHandler::HandleRequestFlagsExperiments,
145                 base::Unretained(this)));
146  web_ui()->RegisterMessageCallback("enableFlagsExperiment",
147      base::Bind(&FlagsDOMHandler::HandleEnableFlagsExperimentMessage,
148                 base::Unretained(this)));
149  web_ui()->RegisterMessageCallback("restartBrowser",
150      base::Bind(&FlagsDOMHandler::HandleRestartBrowser,
151                 base::Unretained(this)));
152  web_ui()->RegisterMessageCallback("resetAllFlags",
153      base::Bind(&FlagsDOMHandler::HandleResetAllFlags,
154                 base::Unretained(this)));
155}
156
157void FlagsDOMHandler::Init(about_flags::FlagsStorage* flags_storage,
158                           about_flags::FlagAccess access) {
159  flags_storage_.reset(flags_storage);
160  access_ = access;
161
162  if (flags_experiments_requested_)
163    HandleRequestFlagsExperiments(NULL);
164}
165
166void FlagsDOMHandler::HandleRequestFlagsExperiments(
167    const base::ListValue* args) {
168  flags_experiments_requested_ = true;
169  // Bail out if the handler hasn't been initialized yet. The request will be
170  // handled after the initialization.
171  if (!flags_storage_)
172    return;
173
174  base::DictionaryValue results;
175
176  scoped_ptr<base::ListValue> supported_experiments(new base::ListValue);
177  scoped_ptr<base::ListValue> unsupported_experiments(new base::ListValue);
178  about_flags::GetFlagsExperimentsData(flags_storage_.get(),
179                                       access_,
180                                       supported_experiments.get(),
181                                       unsupported_experiments.get());
182  results.Set("supportedExperiments", supported_experiments.release());
183  results.Set("unsupportedExperiments", unsupported_experiments.release());
184  results.SetBoolean("needsRestart",
185                     about_flags::IsRestartNeededToCommitChanges());
186  results.SetBoolean("showOwnerWarning",
187                     access_ == about_flags::kGeneralAccessFlagsOnly);
188
189#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
190  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
191  results.SetBoolean("showBetaChannelPromotion",
192                     channel == chrome::VersionInfo::CHANNEL_STABLE);
193  results.SetBoolean("showDevChannelPromotion",
194                     channel == chrome::VersionInfo::CHANNEL_BETA);
195#else
196  results.SetBoolean("showBetaChannelPromotion", false);
197  results.SetBoolean("showDevChannelPromotion", false);
198#endif
199  web_ui()->CallJavascriptFunction("returnFlagsExperiments", results);
200}
201
202void FlagsDOMHandler::HandleEnableFlagsExperimentMessage(
203    const base::ListValue* args) {
204  DCHECK(flags_storage_);
205  DCHECK_EQ(2u, args->GetSize());
206  if (args->GetSize() != 2)
207    return;
208
209  std::string experiment_internal_name;
210  std::string enable_str;
211  if (!args->GetString(0, &experiment_internal_name) ||
212      !args->GetString(1, &enable_str))
213    return;
214
215  about_flags::SetExperimentEnabled(
216      flags_storage_.get(),
217      experiment_internal_name,
218      enable_str == "true");
219}
220
221void FlagsDOMHandler::HandleRestartBrowser(const base::ListValue* args) {
222  DCHECK(flags_storage_);
223#if defined(OS_CHROMEOS)
224  // On ChromeOS be less intrusive and restart inside the user session after
225  // we apply the newly selected flags.
226  CommandLine user_flags(CommandLine::NO_PROGRAM);
227  about_flags::ConvertFlagsToSwitches(flags_storage_.get(),
228                                      &user_flags,
229                                      about_flags::kAddSentinels);
230  CommandLine::StringVector flags;
231  // argv[0] is the program name |CommandLine::NO_PROGRAM|.
232  flags.assign(user_flags.argv().begin() + 1, user_flags.argv().end());
233  VLOG(1) << "Restarting to apply per-session flags...";
234  chromeos::DBusThreadManager::Get()
235      ->GetSessionManagerClient()
236      ->SetFlagsForUser(
237          user_manager::UserManager::Get()->GetActiveUser()->email(), flags);
238#endif
239  chrome::AttemptRestart();
240}
241
242void FlagsDOMHandler::HandleResetAllFlags(const base::ListValue* args) {
243  DCHECK(flags_storage_);
244  about_flags::ResetAllFlags(flags_storage_.get());
245}
246
247
248#if defined(OS_CHROMEOS)
249// On ChromeOS verifying if the owner is signed in is async operation and only
250// after finishing it the UI can be properly populated. This function is the
251// callback for whether the owner is signed in. It will respectively pick the
252// proper PrefService for the flags interface.
253void FinishInitialization(base::WeakPtr<FlagsUI> flags_ui,
254                          Profile* profile,
255                          FlagsDOMHandler* dom_handler,
256                          bool current_user_is_owner) {
257  // If the flags_ui has gone away, there's nothing to do.
258  if (!flags_ui)
259    return;
260
261  // On Chrome OS the owner can set system wide flags and other users can only
262  // set flags for their own session.
263  // Note that |dom_handler| is owned by the web ui that owns |flags_ui|, so
264  // it is still alive if |flags_ui| is.
265  if (current_user_is_owner) {
266    dom_handler->Init(new chromeos::about_flags::OwnerFlagsStorage(
267                          profile->GetPrefs(),
268                          chromeos::CrosSettings::Get()),
269                      about_flags::kOwnerAccessToFlags);
270  } else {
271    dom_handler->Init(
272        new about_flags::PrefServiceFlagsStorage(profile->GetPrefs()),
273        about_flags::kGeneralAccessFlagsOnly);
274  }
275}
276#endif
277
278}  // namespace
279
280///////////////////////////////////////////////////////////////////////////////
281//
282// FlagsUI
283//
284///////////////////////////////////////////////////////////////////////////////
285
286FlagsUI::FlagsUI(content::WebUI* web_ui)
287    : WebUIController(web_ui),
288      weak_factory_(this) {
289  Profile* profile = Profile::FromWebUI(web_ui);
290
291  FlagsDOMHandler* handler = new FlagsDOMHandler();
292  web_ui->AddMessageHandler(handler);
293
294#if defined(OS_CHROMEOS)
295  chromeos::OwnerSettingsServiceChromeOS* service =
296      chromeos::OwnerSettingsServiceChromeOSFactory::GetForProfile(profile);
297  if (service) {
298    service->IsOwnerAsync(base::Bind(
299        &FinishInitialization, weak_factory_.GetWeakPtr(), profile, handler));
300  } else {
301    FinishInitialization(weak_factory_.GetWeakPtr(),
302                         profile,
303                         handler,
304                         false /* current_user_is_owner */);
305  }
306#else
307  handler->Init(new about_flags::PrefServiceFlagsStorage(
308                    g_browser_process->local_state()),
309                about_flags::kOwnerAccessToFlags);
310#endif
311
312  // Set up the about:flags source.
313  content::WebUIDataSource::Add(profile, CreateFlagsUIHTMLSource());
314}
315
316FlagsUI::~FlagsUI() {
317}
318
319// static
320base::RefCountedMemory* FlagsUI::GetFaviconResourceBytes(
321      ui::ScaleFactor scale_factor) {
322  return ResourceBundle::GetSharedInstance().
323      LoadDataResourceBytesForScale(IDR_FLAGS_FAVICON, scale_factor);
324}
325
326// static
327void FlagsUI::RegisterPrefs(PrefRegistrySimple* registry) {
328  registry->RegisterListPref(prefs::kEnabledLabsExperiments);
329}
330
331#if defined(OS_CHROMEOS)
332// static
333void FlagsUI::RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
334  registry->RegisterListPref(prefs::kEnabledLabsExperiments,
335                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
336}
337
338#endif
339