Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / ui / webui / help / help_handler.cc
blobf59ebf883525d05b574197905e813495fc6a3ec7
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.
5 #include "chrome/browser/ui/webui/help/help_handler.h"
7 #include <string>
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/command_line.h"
13 #include "base/strings/string16.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_commands.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/chrome_pages.h"
23 #include "chrome/common/chrome_content_client.h"
24 #include "chrome/common/chrome_version_info.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "chrome/grit/chromium_strings.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "chrome/grit/google_chrome_strings.h"
30 #include "components/google/core/browser/google_util.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "content/public/browser/notification_service.h"
33 #include "content/public/browser/web_ui.h"
34 #include "content/public/common/user_agent.h"
35 #include "grit/components_strings.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "v8/include/v8.h"
39 #if defined(OS_MACOSX)
40 #include "chrome/browser/mac/obsolete_system.h"
41 #endif
43 #if defined(OS_CHROMEOS)
44 #include "base/files/file_util_proxy.h"
45 #include "base/i18n/time_formatting.h"
46 #include "base/prefs/pref_service.h"
47 #include "base/sys_info.h"
48 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
49 #include "chrome/browser/chromeos/settings/cros_settings.h"
50 #include "chrome/browser/profiles/profile.h"
51 #include "chrome/browser/ui/webui/help/help_utils_chromeos.h"
52 #include "chrome/browser/ui/webui/help/version_updater_chromeos.h"
53 #include "chromeos/chromeos_switches.h"
54 #include "chromeos/dbus/dbus_thread_manager.h"
55 #include "chromeos/dbus/power_manager_client.h"
56 #include "components/user_manager/user_manager.h"
57 #endif
59 using base::ListValue;
60 using content::BrowserThread;
62 namespace {
64 #if defined(OS_CHROMEOS)
66 // Returns message that informs user that for update it's better to
67 // connect to a network of one of the allowed types.
68 base::string16 GetAllowedConnectionTypesMessage() {
69 if (help_utils_chromeos::IsUpdateOverCellularAllowed()) {
70 return l10n_util::GetStringUTF16(IDS_UPGRADE_NETWORK_LIST_CELLULAR_ALLOWED);
71 } else {
72 return l10n_util::GetStringUTF16(
73 IDS_UPGRADE_NETWORK_LIST_CELLULAR_DISALLOWED);
77 // Returns true if the device is enterprise managed, false otherwise.
78 bool IsEnterpriseManaged() {
79 policy::BrowserPolicyConnectorChromeOS* connector =
80 g_browser_process->platform_part()->browser_policy_connector_chromeos();
81 return connector->IsEnterpriseManaged();
84 // Returns true if current user can change channel, false otherwise.
85 bool CanChangeChannel() {
86 bool value = false;
87 chromeos::CrosSettings::Get()->GetBoolean(chromeos::kReleaseChannelDelegated,
88 &value);
90 // On a managed machine we delegate this setting to the users of the same
91 // domain only if the policy value is "domain".
92 if (IsEnterpriseManaged()) {
93 if (!value)
94 return false;
95 // Get the currently logged in user and strip the domain part only.
96 std::string domain = "";
97 std::string user =
98 user_manager::UserManager::Get()->GetLoggedInUser()->email();
99 size_t at_pos = user.find('@');
100 if (at_pos != std::string::npos && at_pos + 1 < user.length())
101 domain = user.substr(user.find('@') + 1);
102 policy::BrowserPolicyConnectorChromeOS* connector =
103 g_browser_process->platform_part()->browser_policy_connector_chromeos();
104 return domain == connector->GetEnterpriseDomain();
105 } else if (user_manager::UserManager::Get()->IsCurrentUserOwner()) {
106 // On non managed machines we have local owner who is the only one to change
107 // anything. Ensure that ReleaseChannelDelegated is false.
108 return !value;
110 return false;
113 #endif // defined(OS_CHROMEOS)
115 } // namespace
117 HelpHandler::HelpHandler()
118 : version_updater_(VersionUpdater::Create()),
119 weak_factory_(this) {
122 HelpHandler::~HelpHandler() {
125 void HelpHandler::GetLocalizedValues(base::DictionaryValue* localized_strings) {
126 struct L10nResources {
127 const char* name;
128 int ids;
131 static L10nResources resources[] = {
132 { "aboutTitle", IDS_ABOUT_TITLE },
133 #if defined(OS_CHROMEOS)
134 { "aboutProductTitle", IDS_PRODUCT_OS_NAME },
135 #else
136 { "aboutProductTitle", IDS_PRODUCT_NAME },
137 #endif
138 { "aboutProductDescription", IDS_ABOUT_PRODUCT_DESCRIPTION },
139 { "relaunch", IDS_RELAUNCH_BUTTON },
140 #if defined(OS_CHROMEOS)
141 { "relaunchAndPowerwash", IDS_RELAUNCH_AND_POWERWASH_BUTTON },
142 #endif
143 { "productName", IDS_PRODUCT_NAME },
144 { "updateCheckStarted", IDS_UPGRADE_CHECK_STARTED },
145 { "upToDate", IDS_UPGRADE_UP_TO_DATE },
146 { "updating", IDS_UPGRADE_UPDATING },
147 #if defined(OS_CHROMEOS)
148 { "updateButton", IDS_UPGRADE_BUTTON },
149 { "updatingChannelSwitch", IDS_UPGRADE_UPDATING_CHANNEL_SWITCH },
150 #endif
151 { "updateAlmostDone", IDS_UPGRADE_SUCCESSFUL_RELAUNCH },
152 #if defined(OS_CHROMEOS)
153 { "successfulChannelSwitch", IDS_UPGRADE_SUCCESSFUL_CHANNEL_SWITCH },
154 #endif
155 { "getHelpWithChrome", IDS_GET_HELP_USING_CHROME },
156 { "reportAnIssue", IDS_REPORT_AN_ISSUE },
157 #if defined(OS_CHROMEOS)
158 { "platform", IDS_PLATFORM_LABEL },
159 { "firmware", IDS_ABOUT_PAGE_FIRMWARE },
160 { "showMoreInfo", IDS_SHOW_MORE_INFO },
161 { "hideMoreInfo", IDS_HIDE_MORE_INFO },
162 { "channel", IDS_ABOUT_PAGE_CHANNEL },
163 { "stable", IDS_ABOUT_PAGE_CHANNEL_STABLE },
164 { "beta", IDS_ABOUT_PAGE_CHANNEL_BETA },
165 { "dev", IDS_ABOUT_PAGE_CHANNEL_DEVELOPMENT },
166 { "channel-changed", IDS_ABOUT_PAGE_CHANNEL_CHANGED },
167 { "currentChannelStable", IDS_ABOUT_PAGE_CURRENT_CHANNEL_STABLE },
168 { "currentChannelBeta", IDS_ABOUT_PAGE_CURRENT_CHANNEL_BETA },
169 { "currentChannelDev", IDS_ABOUT_PAGE_CURRENT_CHANNEL_DEV },
170 { "currentChannel", IDS_ABOUT_PAGE_CURRENT_CHANNEL },
171 { "channelChangeButton", IDS_ABOUT_PAGE_CHANNEL_CHANGE_BUTTON },
172 { "channelChangeDisallowedMessage",
173 IDS_ABOUT_PAGE_CHANNEL_CHANGE_DISALLOWED_MESSAGE },
174 { "channelChangePageTitle", IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_TITLE },
175 { "channelChangePagePowerwashTitle",
176 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_POWERWASH_TITLE },
177 { "channelChangePagePowerwashMessage",
178 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_POWERWASH_MESSAGE },
179 { "channelChangePageDelayedChangeTitle",
180 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_DELAYED_CHANGE_TITLE },
181 { "channelChangePageUnstableTitle",
182 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_UNSTABLE_TITLE },
183 { "channelChangePagePowerwashButton",
184 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_POWERWASH_BUTTON },
185 { "channelChangePageChangeButton",
186 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_CHANGE_BUTTON },
187 { "channelChangePageCancelButton",
188 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_CANCEL_BUTTON },
189 { "webkit", IDS_WEBKIT },
190 { "userAgent", IDS_ABOUT_VERSION_USER_AGENT },
191 { "commandLine", IDS_ABOUT_VERSION_COMMAND_LINE },
192 { "buildDate", IDS_ABOUT_VERSION_BUILD_DATE },
193 #endif
194 #if defined(OS_MACOSX)
195 { "promote", IDS_ABOUT_CHROME_PROMOTE_UPDATER },
196 { "learnMore", IDS_LEARN_MORE },
197 #endif
200 for (size_t i = 0; i < arraysize(resources); ++i) {
201 localized_strings->SetString(resources[i].name,
202 l10n_util::GetStringUTF16(resources[i].ids));
205 #if defined(OS_MACOSX)
206 localized_strings->SetString(
207 "updateObsoleteSystem",
208 ObsoleteSystemMac::LocalizedObsoleteSystemString());
209 localized_strings->SetString(
210 "updateObsoleteSystemURL",
211 chrome::kMac32BitDeprecationURL);
212 #endif
214 localized_strings->SetString(
215 "browserVersion",
216 l10n_util::GetStringFUTF16(IDS_ABOUT_PRODUCT_VERSION,
217 BuildBrowserVersionString()));
219 base::Time::Exploded exploded_time;
220 base::Time::Now().LocalExplode(&exploded_time);
221 localized_strings->SetString(
222 "productCopyright",
223 l10n_util::GetStringFUTF16(IDS_ABOUT_VERSION_COPYRIGHT,
224 base::IntToString16(exploded_time.year)));
226 base::string16 license = l10n_util::GetStringFUTF16(
227 IDS_ABOUT_VERSION_LICENSE,
228 base::ASCIIToUTF16(chrome::kChromiumProjectURL),
229 base::ASCIIToUTF16(chrome::kChromeUICreditsURL));
230 localized_strings->SetString("productLicense", license);
232 #if defined(OS_CHROMEOS)
233 base::string16 os_license = l10n_util::GetStringFUTF16(
234 IDS_ABOUT_CROS_VERSION_LICENSE,
235 base::ASCIIToUTF16(chrome::kChromeUIOSCreditsURL));
236 localized_strings->SetString("productOsLicense", os_license);
238 base::string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME);
239 localized_strings->SetString(
240 "channelChangePageDelayedChangeMessage",
241 l10n_util::GetStringFUTF16(
242 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_DELAYED_CHANGE_MESSAGE,
243 product_name));
244 localized_strings->SetString(
245 "channelChangePageUnstableMessage",
246 l10n_util::GetStringFUTF16(
247 IDS_ABOUT_PAGE_CHANNEL_CHANGE_PAGE_UNSTABLE_MESSAGE,
248 product_name));
250 if (CommandLine::ForCurrentProcess()->
251 HasSwitch(chromeos::switches::kDisableNewChannelSwitcherUI)) {
252 localized_strings->SetBoolean("disableNewChannelSwitcherUI", true);
254 #endif
256 base::string16 tos = l10n_util::GetStringFUTF16(
257 IDS_ABOUT_TERMS_OF_SERVICE, base::UTF8ToUTF16(chrome::kChromeUITermsURL));
258 localized_strings->SetString("productTOS", tos);
260 localized_strings->SetString("webkitVersion", content::GetWebKitVersion());
262 localized_strings->SetString("jsEngine", "V8");
263 localized_strings->SetString("jsEngineVersion", v8::V8::GetVersion());
265 localized_strings->SetString("userAgentInfo", GetUserAgent());
267 CommandLine::StringType command_line =
268 CommandLine::ForCurrentProcess()->GetCommandLineString();
269 localized_strings->SetString("commandLineInfo", command_line);
272 void HelpHandler::RegisterMessages() {
273 registrar_.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED,
274 content::NotificationService::AllSources());
276 web_ui()->RegisterMessageCallback("onPageLoaded",
277 base::Bind(&HelpHandler::OnPageLoaded, base::Unretained(this)));
278 web_ui()->RegisterMessageCallback("relaunchNow",
279 base::Bind(&HelpHandler::RelaunchNow, base::Unretained(this)));
280 web_ui()->RegisterMessageCallback("openFeedbackDialog",
281 base::Bind(&HelpHandler::OpenFeedbackDialog, base::Unretained(this)));
282 web_ui()->RegisterMessageCallback("openHelpPage",
283 base::Bind(&HelpHandler::OpenHelpPage, base::Unretained(this)));
284 #if defined(OS_CHROMEOS)
285 web_ui()->RegisterMessageCallback("setChannel",
286 base::Bind(&HelpHandler::SetChannel, base::Unretained(this)));
287 web_ui()->RegisterMessageCallback("relaunchAndPowerwash",
288 base::Bind(&HelpHandler::RelaunchAndPowerwash, base::Unretained(this)));
289 web_ui()->RegisterMessageCallback("requestUpdate",
290 base::Bind(&HelpHandler::RequestUpdate, base::Unretained(this)));
291 #endif
292 #if defined(OS_MACOSX)
293 web_ui()->RegisterMessageCallback("promoteUpdater",
294 base::Bind(&HelpHandler::PromoteUpdater, base::Unretained(this)));
295 #endif
298 void HelpHandler::Observe(int type, const content::NotificationSource& source,
299 const content::NotificationDetails& details) {
300 switch (type) {
301 case chrome::NOTIFICATION_UPGRADE_RECOMMENDED: {
302 // A version update is installed and ready to go. Refresh the UI so the
303 // correct state will be shown.
304 RequestUpdate(NULL);
305 break;
307 default:
308 NOTREACHED();
312 // static
313 base::string16 HelpHandler::BuildBrowserVersionString() {
314 chrome::VersionInfo version_info;
316 std::string version = version_info.Version();
318 std::string modifier = chrome::VersionInfo::GetVersionStringModifier();
319 if (!modifier.empty())
320 version += " " + modifier;
322 #if defined(ARCH_CPU_64_BITS)
323 version += " (64-bit)";
324 #endif
326 return base::UTF8ToUTF16(version);
329 void HelpHandler::OnPageLoaded(const base::ListValue* args) {
330 #if defined(OS_CHROMEOS)
331 // Version information is loaded from a callback
332 loader_.GetVersion(
333 chromeos::VersionLoader::VERSION_FULL,
334 base::Bind(&HelpHandler::OnOSVersion, base::Unretained(this)),
335 &tracker_);
336 loader_.GetFirmware(
337 base::Bind(&HelpHandler::OnOSFirmware, base::Unretained(this)),
338 &tracker_);
340 web_ui()->CallJavascriptFunction(
341 "help.HelpPage.updateEnableReleaseChannel",
342 base::FundamentalValue(CanChangeChannel()));
344 base::Time build_time = base::SysInfo::GetLsbReleaseTime();
345 base::string16 build_date = base::TimeFormatFriendlyDate(build_time);
346 web_ui()->CallJavascriptFunction("help.HelpPage.setBuildDate",
347 base::StringValue(build_date));
348 #endif // defined(OS_CHROMEOS)
350 // On Chrome OS, do not check for an update automatically.
351 #if defined(OS_CHROMEOS)
352 static_cast<VersionUpdaterCros*>(version_updater_.get())->GetUpdateStatus(
353 base::Bind(&HelpHandler::SetUpdateStatus, base::Unretained(this)));
354 #else
355 RequestUpdate(NULL);
356 #endif
358 #if defined(OS_MACOSX)
359 web_ui()->CallJavascriptFunction(
360 "help.HelpPage.setObsoleteSystem",
361 base::FundamentalValue(ObsoleteSystemMac::Is32BitObsoleteNowOrSoon() &&
362 ObsoleteSystemMac::Has32BitOnlyCPU()));
363 web_ui()->CallJavascriptFunction(
364 "help.HelpPage.setObsoleteSystemEndOfTheLine",
365 base::FundamentalValue(ObsoleteSystemMac::Is32BitObsoleteNowOrSoon() &&
366 ObsoleteSystemMac::Is32BitEndOfTheLine()));
367 #endif
369 #if defined(OS_CHROMEOS)
370 web_ui()->CallJavascriptFunction(
371 "help.HelpPage.updateIsEnterpriseManaged",
372 base::FundamentalValue(IsEnterpriseManaged()));
373 // First argument to GetChannel() is a flag that indicates whether
374 // current channel should be returned (if true) or target channel
375 // (otherwise).
376 version_updater_->GetChannel(true,
377 base::Bind(&HelpHandler::OnCurrentChannel, weak_factory_.GetWeakPtr()));
378 version_updater_->GetChannel(false,
379 base::Bind(&HelpHandler::OnTargetChannel, weak_factory_.GetWeakPtr()));
380 #endif
383 #if defined(OS_MACOSX)
384 void HelpHandler::PromoteUpdater(const base::ListValue* args) {
385 version_updater_->PromoteUpdater();
387 #endif
389 void HelpHandler::RelaunchNow(const base::ListValue* args) {
390 DCHECK(args->empty());
391 version_updater_->RelaunchBrowser();
394 void HelpHandler::OpenFeedbackDialog(const base::ListValue* args) {
395 DCHECK(args->empty());
396 Browser* browser = chrome::FindBrowserWithWebContents(
397 web_ui()->GetWebContents());
398 chrome::OpenFeedbackDialog(browser);
401 void HelpHandler::OpenHelpPage(const base::ListValue* args) {
402 DCHECK(args->empty());
403 Browser* browser = chrome::FindBrowserWithWebContents(
404 web_ui()->GetWebContents());
405 chrome::ShowHelp(browser, chrome::HELP_SOURCE_WEBUI);
408 #if defined(OS_CHROMEOS)
410 void HelpHandler::SetChannel(const base::ListValue* args) {
411 DCHECK(args->GetSize() == 2);
413 if (!CanChangeChannel()) {
414 LOG(WARNING) << "Non-owner tried to change release track.";
415 return;
418 base::string16 channel;
419 bool is_powerwash_allowed;
420 if (!args->GetString(0, &channel) ||
421 !args->GetBoolean(1, &is_powerwash_allowed)) {
422 LOG(ERROR) << "Can't parse SetChannel() args";
423 return;
426 version_updater_->SetChannel(base::UTF16ToUTF8(channel),
427 is_powerwash_allowed);
428 if (user_manager::UserManager::Get()->IsCurrentUserOwner()) {
429 // Check for update after switching release channel.
430 version_updater_->CheckForUpdate(base::Bind(&HelpHandler::SetUpdateStatus,
431 base::Unretained(this)));
435 void HelpHandler::RelaunchAndPowerwash(const base::ListValue* args) {
436 DCHECK(args->empty());
438 if (IsEnterpriseManaged())
439 return;
441 PrefService* prefs = g_browser_process->local_state();
442 prefs->SetBoolean(prefs::kFactoryResetRequested, true);
443 prefs->CommitPendingWrite();
445 // Perform sign out. Current chrome process will then terminate, new one will
446 // be launched (as if it was a restart).
447 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
450 #endif // defined(OS_CHROMEOS)
452 void HelpHandler::RequestUpdate(const base::ListValue* args) {
453 version_updater_->CheckForUpdate(
454 base::Bind(&HelpHandler::SetUpdateStatus, base::Unretained(this))
455 #if defined(OS_MACOSX)
456 , base::Bind(&HelpHandler::SetPromotionState, base::Unretained(this))
457 #endif
461 void HelpHandler::SetUpdateStatus(VersionUpdater::Status status,
462 int progress, const base::string16& message) {
463 // Only UPDATING state should have progress set.
464 DCHECK(status == VersionUpdater::UPDATING || progress == 0);
466 std::string status_str;
467 switch (status) {
468 case VersionUpdater::CHECKING:
469 status_str = "checking";
470 break;
471 case VersionUpdater::UPDATING:
472 status_str = "updating";
473 break;
474 case VersionUpdater::NEARLY_UPDATED:
475 status_str = "nearly_updated";
476 break;
477 case VersionUpdater::UPDATED:
478 status_str = "updated";
479 break;
480 case VersionUpdater::FAILED:
481 case VersionUpdater::FAILED_OFFLINE:
482 case VersionUpdater::FAILED_CONNECTION_TYPE_DISALLOWED:
483 status_str = "failed";
484 break;
485 case VersionUpdater::DISABLED:
486 status_str = "disabled";
487 break;
490 web_ui()->CallJavascriptFunction("help.HelpPage.setUpdateStatus",
491 base::StringValue(status_str),
492 base::StringValue(message));
494 if (status == VersionUpdater::UPDATING) {
495 web_ui()->CallJavascriptFunction("help.HelpPage.setProgress",
496 base::FundamentalValue(progress));
499 #if defined(OS_CHROMEOS)
500 if (status == VersionUpdater::FAILED_OFFLINE ||
501 status == VersionUpdater::FAILED_CONNECTION_TYPE_DISALLOWED) {
502 base::string16 types_msg = GetAllowedConnectionTypesMessage();
503 if (!types_msg.empty()) {
504 web_ui()->CallJavascriptFunction(
505 "help.HelpPage.setAndShowAllowedConnectionTypesMsg",
506 base::StringValue(types_msg));
507 } else {
508 web_ui()->CallJavascriptFunction(
509 "help.HelpPage.showAllowedConnectionTypesMsg",
510 base::FundamentalValue(false));
512 } else {
513 web_ui()->CallJavascriptFunction(
514 "help.HelpPage.showAllowedConnectionTypesMsg",
515 base::FundamentalValue(false));
517 #endif // defined(OS_CHROMEOS)
520 #if defined(OS_MACOSX)
521 void HelpHandler::SetPromotionState(VersionUpdater::PromotionState state) {
522 std::string state_str;
523 switch (state) {
524 case VersionUpdater::PROMOTE_HIDDEN:
525 state_str = "hidden";
526 break;
527 case VersionUpdater::PROMOTE_ENABLED:
528 state_str = "enabled";
529 break;
530 case VersionUpdater::PROMOTE_DISABLED:
531 state_str = "disabled";
532 break;
535 web_ui()->CallJavascriptFunction("help.HelpPage.setPromotionState",
536 base::StringValue(state_str));
538 #endif // defined(OS_MACOSX)
540 #if defined(OS_CHROMEOS)
541 void HelpHandler::OnOSVersion(const std::string& version) {
542 web_ui()->CallJavascriptFunction("help.HelpPage.setOSVersion",
543 base::StringValue(version));
546 void HelpHandler::OnOSFirmware(const std::string& firmware) {
547 web_ui()->CallJavascriptFunction("help.HelpPage.setOSFirmware",
548 base::StringValue(firmware));
551 void HelpHandler::OnCurrentChannel(const std::string& channel) {
552 web_ui()->CallJavascriptFunction(
553 "help.HelpPage.updateCurrentChannel", base::StringValue(channel));
556 void HelpHandler::OnTargetChannel(const std::string& channel) {
557 web_ui()->CallJavascriptFunction(
558 "help.HelpPage.updateTargetChannel", base::StringValue(channel));
561 #endif // defined(OS_CHROMEOS)