| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/web_applications/web_app_mac.h" | 5 #import "chrome/browser/web_applications/web_app_mac.h" |
| 6 | 6 |
| 7 #import <Cocoa/Cocoa.h> | 7 #import <Cocoa/Cocoa.h> |
| 8 #include <stdint.h> | 8 #include <stdint.h> |
| 9 | 9 |
| 10 #include <utility> | 10 #include <utility> |
| (...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 207 NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); | 207 NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); |
| 208 base::FilePath user_data_dir; | 208 base::FilePath user_data_dir; |
| 209 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); | 209 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); |
| 210 DCHECK(!user_data_dir.empty()); | 210 DCHECK(!user_data_dir.empty()); |
| 211 return base::StartsWith( | 211 return base::StartsWith( |
| 212 base::SysNSStringToUTF8( | 212 base::SysNSStringToUTF8( |
| 213 [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]), | 213 [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]), |
| 214 user_data_dir.value(), base::CompareCase::SENSITIVE); | 214 user_data_dir.value(), base::CompareCase::SENSITIVE); |
| 215 } | 215 } |
| 216 | 216 |
| 217 void LaunchShimOnFileThread( | 217 void LaunchShimOnFileThread(scoped_refptr<web_app::ShortcutInfo> shortcut_info, |
| 218 std::unique_ptr<web_app::ShortcutInfo> shortcut_info, | 218 bool launched_after_rebuild) { |
| 219 bool launched_after_rebuild) { | |
| 220 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); | 219 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); |
| 221 base::FilePath shim_path = web_app::GetAppInstallPath(*shortcut_info); | 220 base::FilePath shim_path = web_app::GetAppInstallPath(*shortcut_info); |
| 222 | 221 |
| 223 if (shim_path.empty() || | 222 if (shim_path.empty() || |
| 224 !base::PathExists(shim_path) || | 223 !base::PathExists(shim_path) || |
| 225 !HasSameUserDataDir(shim_path)) { | 224 !HasSameUserDataDir(shim_path)) { |
| 226 // The user may have deleted the copy in the Applications folder, use the | 225 // The user may have deleted the copy in the Applications folder, use the |
| 227 // one in the web app's |app_data_dir_|. | 226 // one in the web app's |app_data_dir_|. |
| 228 base::FilePath app_data_dir = web_app::GetWebAppDataDirectory( | 227 base::FilePath app_data_dir = web_app::GetWebAppDataDirectory( |
| 229 shortcut_info->profile_path, shortcut_info->extension_id, GURL()); | 228 shortcut_info->profile_path, shortcut_info->extension_id, GURL()); |
| (...skipping 29 matching lines...) Expand all Loading... |
| 259 !g_app_shims_allow_update_and_launch_in_tests) { | 258 !g_app_shims_allow_update_and_launch_in_tests) { |
| 260 return; | 259 return; |
| 261 } | 260 } |
| 262 | 261 |
| 263 web_app::WebAppShortcutCreator shortcut_creator(app_data_path, | 262 web_app::WebAppShortcutCreator shortcut_creator(app_data_path, |
| 264 &shortcut_info); | 263 &shortcut_info); |
| 265 shortcut_creator.UpdateShortcuts(); | 264 shortcut_creator.UpdateShortcuts(); |
| 266 } | 265 } |
| 267 | 266 |
| 268 void UpdateAndLaunchShimOnFileThread( | 267 void UpdateAndLaunchShimOnFileThread( |
| 269 std::unique_ptr<web_app::ShortcutInfo> shortcut_info) { | 268 scoped_refptr<web_app::ShortcutInfo> shortcut_info) { |
| 270 base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory( | 269 base::FilePath shortcut_data_dir = web_app::GetWebAppDataDirectory( |
| 271 shortcut_info->profile_path, shortcut_info->extension_id, GURL()); | 270 shortcut_info->profile_path, shortcut_info->extension_id, GURL()); |
| 272 UpdatePlatformShortcutsInternal(shortcut_data_dir, base::string16(), | 271 UpdatePlatformShortcutsInternal(shortcut_data_dir, base::string16(), |
| 273 *shortcut_info); | 272 *shortcut_info); |
| 274 LaunchShimOnFileThread(std::move(shortcut_info), true); | 273 LaunchShimOnFileThread(std::move(shortcut_info), true); |
| 275 } | 274 } |
| 276 | 275 |
| 277 void UpdateAndLaunchShim(std::unique_ptr<web_app::ShortcutInfo> shortcut_info) { | 276 void UpdateAndLaunchShim(scoped_refptr<web_app::ShortcutInfo> shortcut_info) { |
| 278 content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, | 277 content::BrowserThread::PostTask( |
| 279 base::Bind(&UpdateAndLaunchShimOnFileThread, | 278 content::BrowserThread::FILE, FROM_HERE, |
| 280 base::Passed(&shortcut_info))); | 279 base::Bind(&UpdateAndLaunchShimOnFileThread, std::move(shortcut_info))); |
| 281 } | 280 } |
| 282 | 281 |
| 283 void RebuildAppAndLaunch(std::unique_ptr<web_app::ShortcutInfo> shortcut_info) { | 282 void RebuildAppAndLaunch(scoped_refptr<web_app::ShortcutInfo> shortcut_info) { |
| 284 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 283 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 285 if (shortcut_info->extension_id == app_mode::kAppListModeId) { | 284 if (shortcut_info->extension_id == app_mode::kAppListModeId) { |
| 286 AppListService* app_list_service = AppListService::Get(); | 285 AppListService* app_list_service = AppListService::Get(); |
| 287 app_list_service->CreateShortcut(); | 286 app_list_service->CreateShortcut(); |
| 288 app_list_service->Show(); | 287 app_list_service->Show(); |
| 289 return; | 288 return; |
| 290 } | 289 } |
| 291 | 290 |
| 292 ProfileManager* profile_manager = g_browser_process->profile_manager(); | 291 ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| 293 Profile* profile = | 292 Profile* profile = |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 470 base::FileEnumerator::DIRECTORIES); | 469 base::FileEnumerator::DIRECTORIES); |
| 471 for (base::FilePath bundle_path = enumerator.Next(); | 470 for (base::FilePath bundle_path = enumerator.Next(); |
| 472 !bundle_path.empty(); bundle_path = enumerator.Next()) { | 471 !bundle_path.empty(); bundle_path = enumerator.Next()) { |
| 473 if (IsShimForProfile(bundle_path.BaseName(), profile_base_name)) | 472 if (IsShimForProfile(bundle_path.BaseName(), profile_base_name)) |
| 474 bundle_paths.push_back(bundle_path); | 473 bundle_paths.push_back(bundle_path); |
| 475 } | 474 } |
| 476 | 475 |
| 477 return bundle_paths; | 476 return bundle_paths; |
| 478 } | 477 } |
| 479 | 478 |
| 480 std::unique_ptr<web_app::ShortcutInfo> BuildShortcutInfoFromBundle( | 479 scoped_refptr<web_app::ShortcutInfo> BuildShortcutInfoFromBundle( |
| 481 const base::FilePath& bundle_path) { | 480 const base::FilePath& bundle_path) { |
| 482 NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); | 481 NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); |
| 483 | 482 |
| 484 std::unique_ptr<web_app::ShortcutInfo> shortcut_info( | 483 scoped_refptr<web_app::ShortcutInfo> shortcut_info(new web_app::ShortcutInfo); |
| 485 new web_app::ShortcutInfo); | |
| 486 shortcut_info->extension_id = base::SysNSStringToUTF8( | 484 shortcut_info->extension_id = base::SysNSStringToUTF8( |
| 487 [plist valueForKey:app_mode::kCrAppModeShortcutIDKey]); | 485 [plist valueForKey:app_mode::kCrAppModeShortcutIDKey]); |
| 488 shortcut_info->is_platform_app = true; | 486 shortcut_info->is_platform_app = true; |
| 489 shortcut_info->url = GURL(base::SysNSStringToUTF8( | 487 shortcut_info->url = GURL(base::SysNSStringToUTF8( |
| 490 [plist valueForKey:app_mode::kCrAppModeShortcutURLKey])); | 488 [plist valueForKey:app_mode::kCrAppModeShortcutURLKey])); |
| 491 shortcut_info->title = base::SysNSStringToUTF16( | 489 shortcut_info->title = base::SysNSStringToUTF16( |
| 492 [plist valueForKey:app_mode::kCrAppModeShortcutNameKey]); | 490 [plist valueForKey:app_mode::kCrAppModeShortcutNameKey]); |
| 493 shortcut_info->profile_name = base::SysNSStringToUTF8( | 491 shortcut_info->profile_name = base::SysNSStringToUTF8( |
| 494 [plist valueForKey:app_mode::kCrAppModeProfileNameKey]); | 492 [plist valueForKey:app_mode::kCrAppModeProfileNameKey]); |
| 495 | 493 |
| 496 // Figure out the profile_path. Since the user_data_dir could contain the | 494 // Figure out the profile_path. Since the user_data_dir could contain the |
| 497 // path to the web app data dir. | 495 // path to the web app data dir. |
| 498 base::FilePath user_data_dir = base::mac::NSStringToFilePath( | 496 base::FilePath user_data_dir = base::mac::NSStringToFilePath( |
| 499 [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]); | 497 [plist valueForKey:app_mode::kCrAppModeUserDataDirKey]); |
| 500 base::FilePath profile_base_name = base::mac::NSStringToFilePath( | 498 base::FilePath profile_base_name = base::mac::NSStringToFilePath( |
| 501 [plist valueForKey:app_mode::kCrAppModeProfileDirKey]); | 499 [plist valueForKey:app_mode::kCrAppModeProfileDirKey]); |
| 502 if (user_data_dir.DirName().DirName().BaseName() == profile_base_name) | 500 if (user_data_dir.DirName().DirName().BaseName() == profile_base_name) |
| 503 shortcut_info->profile_path = user_data_dir.DirName().DirName(); | 501 shortcut_info->profile_path = user_data_dir.DirName().DirName(); |
| 504 else | 502 else |
| 505 shortcut_info->profile_path = user_data_dir.Append(profile_base_name); | 503 shortcut_info->profile_path = user_data_dir.Append(profile_base_name); |
| 506 | 504 |
| 507 return shortcut_info; | 505 return shortcut_info; |
| 508 } | 506 } |
| 509 | 507 |
| 510 std::unique_ptr<web_app::ShortcutInfo> RecordAppShimErrorAndBuildShortcutInfo( | 508 scoped_refptr<web_app::ShortcutInfo> RecordAppShimErrorAndBuildShortcutInfo( |
| 511 const base::FilePath& bundle_path) { | 509 const base::FilePath& bundle_path) { |
| 512 NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); | 510 NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); |
| 513 NSString* version_string = [plist valueForKey:app_mode::kCrBundleVersionKey]; | 511 NSString* version_string = [plist valueForKey:app_mode::kCrBundleVersionKey]; |
| 514 if (!version_string) { | 512 if (!version_string) { |
| 515 // Older bundles have the Chrome version in the following key. | 513 // Older bundles have the Chrome version in the following key. |
| 516 version_string = | 514 version_string = |
| 517 [plist valueForKey:app_mode::kCFBundleShortVersionStringKey]; | 515 [plist valueForKey:app_mode::kCFBundleShortVersionStringKey]; |
| 518 } | 516 } |
| 519 base::Version full_version(base::SysNSStringToUTF8(version_string)); | 517 base::Version full_version(base::SysNSStringToUTF8(version_string)); |
| 520 uint32_t major_version = 0; | 518 uint32_t major_version = 0; |
| 521 if (full_version.IsValid()) | 519 if (full_version.IsValid()) |
| 522 major_version = full_version.components()[0]; | 520 major_version = full_version.components()[0]; |
| 523 UMA_HISTOGRAM_SPARSE_SLOWLY("Apps.AppShimErrorVersion", major_version); | 521 UMA_HISTOGRAM_SPARSE_SLOWLY("Apps.AppShimErrorVersion", major_version); |
| 524 | 522 |
| 525 return BuildShortcutInfoFromBundle(bundle_path); | 523 return BuildShortcutInfoFromBundle(bundle_path); |
| 526 } | 524 } |
| 527 | 525 |
| 528 void RevealAppShimInFinderForAppOnFileThread( | 526 void RevealAppShimInFinderForAppOnFileThread( |
| 529 std::unique_ptr<web_app::ShortcutInfo> shortcut_info, | 527 scoped_refptr<web_app::ShortcutInfo> shortcut_info, |
| 530 const base::FilePath& app_path) { | 528 const base::FilePath& app_path) { |
| 531 web_app::WebAppShortcutCreator shortcut_creator(app_path, | 529 web_app::WebAppShortcutCreator shortcut_creator(app_path, |
| 532 shortcut_info.get()); | 530 shortcut_info.get()); |
| 533 shortcut_creator.RevealAppShimInFinder(); | 531 shortcut_creator.RevealAppShimInFinder(); |
| 534 } | 532 } |
| 535 | 533 |
| 536 // Mac-specific version of web_app::ShouldCreateShortcutFor() used during batch | 534 // Mac-specific version of web_app::ShouldCreateShortcutFor() used during batch |
| 537 // upgrades to ensure all shortcuts a user may still have are repaired when | 535 // upgrades to ensure all shortcuts a user may still have are repaired when |
| 538 // required by a Chrome upgrade. | 536 // required by a Chrome upgrade. |
| 539 bool ShouldUpgradeShortcutFor(Profile* profile, | 537 bool ShouldUpgradeShortcutFor(Profile* profile, |
| (...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 964 // that directory in Finder. | 962 // that directory in Finder. |
| 965 [[NSWorkspace sharedWorkspace] | 963 [[NSWorkspace sharedWorkspace] |
| 966 openFile:base::mac::FilePathToNSString(app_path)]; | 964 openFile:base::mac::FilePathToNSString(app_path)]; |
| 967 } | 965 } |
| 968 | 966 |
| 969 base::FilePath GetAppInstallPath(const ShortcutInfo& shortcut_info) { | 967 base::FilePath GetAppInstallPath(const ShortcutInfo& shortcut_info) { |
| 970 WebAppShortcutCreator shortcut_creator(base::FilePath(), &shortcut_info); | 968 WebAppShortcutCreator shortcut_creator(base::FilePath(), &shortcut_info); |
| 971 return shortcut_creator.GetApplicationsShortcutPath(); | 969 return shortcut_creator.GetApplicationsShortcutPath(); |
| 972 } | 970 } |
| 973 | 971 |
| 974 void MaybeLaunchShortcut(std::unique_ptr<ShortcutInfo> shortcut_info) { | 972 void MaybeLaunchShortcut(scoped_refptr<ShortcutInfo> shortcut_info) { |
| 975 if (AppShimsDisabledForTest() && | 973 if (AppShimsDisabledForTest() && |
| 976 !g_app_shims_allow_update_and_launch_in_tests) { | 974 !g_app_shims_allow_update_and_launch_in_tests) { |
| 977 return; | 975 return; |
| 978 } | 976 } |
| 979 | 977 |
| 980 content::BrowserThread::PostTask( | 978 content::BrowserThread::PostTask( |
| 981 content::BrowserThread::FILE, FROM_HERE, | 979 content::BrowserThread::FILE, FROM_HERE, |
| 982 base::Bind(&LaunchShimOnFileThread, base::Passed(&shortcut_info), false)); | 980 base::Bind(&LaunchShimOnFileThread, std::move(shortcut_info), false)); |
| 983 } | 981 } |
| 984 | 982 |
| 985 bool MaybeRebuildShortcut(const base::CommandLine& command_line) { | 983 bool MaybeRebuildShortcut(const base::CommandLine& command_line) { |
| 986 if (!command_line.HasSwitch(app_mode::kAppShimError)) | 984 if (!command_line.HasSwitch(app_mode::kAppShimError)) |
| 987 return false; | 985 return false; |
| 988 | 986 |
| 989 base::PostTaskWithTraitsAndReplyWithResult( | 987 base::PostTaskWithTraitsAndReplyWithResult( |
| 990 FROM_HERE, base::TaskTraits().MayBlock().WithPriority( | 988 FROM_HERE, base::TaskTraits().MayBlock().WithPriority( |
| 991 base::TaskPriority::BACKGROUND), | 989 base::TaskPriority::BACKGROUND), |
| 992 base::Bind(&RecordAppShimErrorAndBuildShortcutInfo, | 990 base::Bind(&RecordAppShimErrorAndBuildShortcutInfo, |
| (...skipping 20 matching lines...) Expand all Loading... |
| 1013 const extensions::Extension* extension = extension_refptr.get(); | 1011 const extensions::Extension* extension = extension_refptr.get(); |
| 1014 if (ShouldUpgradeShortcutFor(profile, extension)) { | 1012 if (ShouldUpgradeShortcutFor(profile, extension)) { |
| 1015 web_app::UpdateAllShortcuts(base::string16(), profile, extension, | 1013 web_app::UpdateAllShortcuts(base::string16(), profile, extension, |
| 1016 latch->NoOpClosure()); | 1014 latch->NoOpClosure()); |
| 1017 } | 1015 } |
| 1018 } | 1016 } |
| 1019 } | 1017 } |
| 1020 | 1018 |
| 1021 void RevealAppShimInFinderForApp(Profile* profile, | 1019 void RevealAppShimInFinderForApp(Profile* profile, |
| 1022 const extensions::Extension* app) { | 1020 const extensions::Extension* app) { |
| 1023 std::unique_ptr<web_app::ShortcutInfo> shortcut_info = | 1021 scoped_refptr<web_app::ShortcutInfo> shortcut_info = |
| 1024 ShortcutInfoForExtensionAndProfile(app, profile); | 1022 ShortcutInfoForExtensionAndProfile(app, profile); |
| 1025 content::BrowserThread::PostTask( | 1023 content::BrowserThread::PostTask( |
| 1026 content::BrowserThread::FILE, FROM_HERE, | 1024 content::BrowserThread::FILE, FROM_HERE, |
| 1027 base::Bind(&RevealAppShimInFinderForAppOnFileThread, | 1025 base::Bind(&RevealAppShimInFinderForAppOnFileThread, |
| 1028 base::Passed(&shortcut_info), app->path())); | 1026 std::move(shortcut_info), app->path())); |
| 1029 } | 1027 } |
| 1030 | 1028 |
| 1031 namespace internals { | 1029 namespace internals { |
| 1032 | 1030 |
| 1033 bool CreatePlatformShortcuts( | 1031 bool CreatePlatformShortcuts(const base::FilePath& app_data_path, |
| 1034 const base::FilePath& app_data_path, | 1032 scoped_refptr<ShortcutInfo> shortcut_info, |
| 1035 std::unique_ptr<ShortcutInfo> shortcut_info, | 1033 const ShortcutLocations& creation_locations, |
| 1036 const ShortcutLocations& creation_locations, | 1034 ShortcutCreationReason creation_reason) { |
| 1037 ShortcutCreationReason creation_reason) { | |
| 1038 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); | 1035 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); |
| 1039 if (AppShimsDisabledForTest()) | 1036 if (AppShimsDisabledForTest()) |
| 1040 return true; | 1037 return true; |
| 1041 | 1038 |
| 1042 WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get()); | 1039 WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get()); |
| 1043 return shortcut_creator.CreateShortcuts(creation_reason, creation_locations); | 1040 return shortcut_creator.CreateShortcuts(creation_reason, creation_locations); |
| 1044 } | 1041 } |
| 1045 | 1042 |
| 1046 void DeletePlatformShortcuts(const base::FilePath& app_data_path, | 1043 void DeletePlatformShortcuts(const base::FilePath& app_data_path, |
| 1047 std::unique_ptr<ShortcutInfo> shortcut_info) { | 1044 scoped_refptr<ShortcutInfo> shortcut_info) { |
| 1048 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); | 1045 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); |
| 1049 WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get()); | 1046 WebAppShortcutCreator shortcut_creator(app_data_path, shortcut_info.get()); |
| 1050 shortcut_creator.DeleteShortcuts(); | 1047 shortcut_creator.DeleteShortcuts(); |
| 1051 } | 1048 } |
| 1052 | 1049 |
| 1053 void UpdatePlatformShortcuts(const base::FilePath& app_data_path, | 1050 void UpdatePlatformShortcuts(const base::FilePath& app_data_path, |
| 1054 const base::string16& old_app_title, | 1051 const base::string16& old_app_title, |
| 1055 std::unique_ptr<ShortcutInfo> shortcut_info) { | 1052 scoped_refptr<ShortcutInfo> shortcut_info) { |
| 1056 UpdatePlatformShortcutsInternal(app_data_path, old_app_title, *shortcut_info); | 1053 UpdatePlatformShortcutsInternal(app_data_path, old_app_title, *shortcut_info); |
| 1057 } | 1054 } |
| 1058 | 1055 |
| 1059 void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) { | 1056 void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) { |
| 1060 const std::string profile_base_name = profile_path.BaseName().value(); | 1057 const std::string profile_base_name = profile_path.BaseName().value(); |
| 1061 std::vector<base::FilePath> bundles = GetAllAppBundlesInPath( | 1058 std::vector<base::FilePath> bundles = GetAllAppBundlesInPath( |
| 1062 profile_path.Append(chrome::kWebAppDirname), profile_base_name); | 1059 profile_path.Append(chrome::kWebAppDirname), profile_base_name); |
| 1063 | 1060 |
| 1064 for (std::vector<base::FilePath>::const_iterator it = bundles.begin(); | 1061 for (std::vector<base::FilePath>::const_iterator it = bundles.begin(); |
| 1065 it != bundles.end(); ++it) { | 1062 it != bundles.end(); ++it) { |
| 1066 std::unique_ptr<web_app::ShortcutInfo> shortcut_info = | 1063 scoped_refptr<web_app::ShortcutInfo> shortcut_info = |
| 1067 BuildShortcutInfoFromBundle(*it); | 1064 BuildShortcutInfoFromBundle(*it); |
| 1068 WebAppShortcutCreator shortcut_creator(it->DirName(), shortcut_info.get()); | 1065 WebAppShortcutCreator shortcut_creator(it->DirName(), shortcut_info.get()); |
| 1069 shortcut_creator.DeleteShortcuts(); | 1066 shortcut_creator.DeleteShortcuts(); |
| 1070 } | 1067 } |
| 1071 } | 1068 } |
| 1072 | 1069 |
| 1073 } // namespace internals | 1070 } // namespace internals |
| 1074 | 1071 |
| 1075 } // namespace web_app | 1072 } // namespace web_app |
| OLD | NEW |