Create and delete desktop shortcuts in general.

Currently planning 6 steps for this feature:
1. (this step) Create/delete shortcuts in general (few edge cases)
2. Add functionality for the checkbox- allow the user to select whether they want a shortcut or not from the create/manage profile dialogs
3. Find shortcuts on the desktop by their arguments instead of the path.
4. Handle edge case of 1 to 2 and 2 to 1 profiles (adding/removing icon and name from the shortcut)
5. Update shortcuts when the user changes their name/icon
6. Repopulate shortcuts map when browser starts up (to allow delete/update on new browser instances) 

This CL depends on: Issue 10826188

BUG=133585
TEST=manual testing

Review URL: https://chromiumcodereview.appspot.com/10823217

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151974 0039d316-1c4b-4281-b951-d872f2087c98
parent 77363287
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#include "chrome/browser/media_gallery/media_device_notifications_window_win.h" #include "chrome/browser/media_gallery/media_device_notifications_window_win.h"
#include "chrome/browser/metrics/metrics_service.h" #include "chrome/browser/metrics/metrics_service.h"
#include "chrome/browser/profiles/profile_info_cache.h" #include "chrome/browser/profiles/profile_info_cache.h"
#include "chrome/browser/profiles/profile_shortcut_manager_win.h" #include "chrome/browser/profiles/profile_shortcut_manager.h"
#include "chrome/browser/ui/simple_message_box.h" #include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/uninstall_browser_prompt.h" #include "chrome/browser/ui/uninstall_browser_prompt.h"
#include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_constants.h"
...@@ -132,11 +132,7 @@ int DoUninstallTasks(bool chrome_still_running) { ...@@ -132,11 +132,7 @@ int DoUninstallTasks(bool chrome_still_running) {
dist, ShellUtil::CURRENT_USER, ShellUtil::SHORTCUT_NO_OPTIONS)) { dist, ShellUtil::CURRENT_USER, ShellUtil::SHORTCUT_NO_OPTIONS)) {
VLOG(1) << "Failed to delete desktop shortcut."; VLOG(1) << "Failed to delete desktop shortcut.";
} }
if (!ShellUtil::RemoveChromeDesktopShortcutsWithAppendedNames( // TODO(hallielaine): Cleanup profiles shortcuts.
ProfileShortcutManagerWin::GenerateShortcutsFromProfiles(
ProfileInfoCache::GetProfileNames()))) {
VLOG(1) << "Failed to delete desktop profiles shortcuts.";
}
if (!ShellUtil::RemoveChromeQuickLaunchShortcut(dist, if (!ShellUtil::RemoveChromeQuickLaunchShortcut(dist,
ShellUtil::CURRENT_USER)) { ShellUtil::CURRENT_USER)) {
VLOG(1) << "Failed to delete quick launch shortcut."; VLOG(1) << "Failed to delete quick launch shortcut.";
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/string_number_conversions.h" #include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/avatar_menu_model_observer.h" #include "chrome/browser/profiles/avatar_menu_model_observer.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
...@@ -102,7 +103,21 @@ void AvatarMenuModel::EditProfile(size_t index) { ...@@ -102,7 +103,21 @@ void AvatarMenuModel::EditProfile(size_t index) {
} }
void AvatarMenuModel::AddNewProfile() { void AvatarMenuModel::AddNewProfile() {
// Temporarily generate a random icon/name pair to create the profile with
// instead of opening a dialog for creating a profile.
// Remove on completion of BUG 134904.
if (ProfileShortcutManager::IsFeatureEnabled()) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
ProfileInfoCache& cache = profile_manager->GetProfileInfoCache();
size_t icon_index = cache.ChooseAvatarIconIndexForNewProfile();
g_browser_process->profile_manager()->CreateMultiProfileAsync(
cache.ChooseNameForNewProfile(icon_index),
ASCIIToUTF16(cache.GetDefaultAvatarIconUrl(icon_index)));
} else {
ProfileManager::CreateMultiProfileAsync(string16(), string16()); ProfileManager::CreateMultiProfileAsync(string16(), string16());
}
ProfileMetrics::LogProfileAddNewUser(ProfileMetrics::ADD_NEW_USER_ICON); ProfileMetrics::LogProfileAddNewUser(ProfileMetrics::ADD_NEW_USER_ICON);
} }
......
...@@ -239,6 +239,7 @@ ProfileManager::ProfileManager(const FilePath& user_data_dir) ...@@ -239,6 +239,7 @@ ProfileManager::ProfileManager(const FilePath& user_data_dir)
: user_data_dir_(user_data_dir), : user_data_dir_(user_data_dir),
logged_in_(false), logged_in_(false),
will_import_(false), will_import_(false),
profile_shortcut_manager_(NULL),
#if !defined(OS_ANDROID) #if !defined(OS_ANDROID)
ALLOW_THIS_IN_INITIALIZER_LIST( ALLOW_THIS_IN_INITIALIZER_LIST(
browser_list_observer_(this)), browser_list_observer_(this)),
...@@ -266,13 +267,12 @@ ProfileManager::ProfileManager(const FilePath& user_data_dir) ...@@ -266,13 +267,12 @@ ProfileManager::ProfileManager(const FilePath& user_data_dir)
this, this,
chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
content::NotificationService::AllSources()); content::NotificationService::AllSources());
if (ProfileShortcutManager::IsFeatureEnabled())
profile_shortcut_manager_.reset(ProfileShortcutManager::Create());
} }
ProfileManager::~ProfileManager() { ProfileManager::~ProfileManager() {
#if defined(OS_WIN)
if (profile_shortcut_manager_.get())
profile_info_cache_->RemoveObserver(profile_shortcut_manager_.get());
#endif
} }
FilePath ProfileManager::GetDefaultProfileDir( FilePath ProfileManager::GetDefaultProfileDir(
...@@ -454,6 +454,15 @@ void ProfileManager::CreateProfileAsync(const FilePath& profile_path, ...@@ -454,6 +454,15 @@ void ProfileManager::CreateProfileAsync(const FilePath& profile_path,
cache.AddProfileToCache(profile_path, name, string16(), icon_index); cache.AddProfileToCache(profile_path, name, string16(), icon_index);
} }
info->callbacks.push_back(callback); info->callbacks.push_back(callback);
if (profile_shortcut_manager_.get() && !name.empty() &&
!icon_url.empty()) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ProfileShortcutManager::CreateChromeDesktopShortcut,
base::Unretained(profile_shortcut_manager_.get()), profile_path, name,
ResourceBundle::GetSharedInstance().GetNativeImageNamed(
cache.GetDefaultAvatarIconResourceIDAtIndex(icon_index))));
}
} }
} }
...@@ -680,9 +689,6 @@ void ProfileManager::DoFinalInit(Profile* profile, bool go_off_the_record) { ...@@ -680,9 +689,6 @@ void ProfileManager::DoFinalInit(Profile* profile, bool go_off_the_record) {
InitProfileUserPrefs(profile); InitProfileUserPrefs(profile);
AddProfileToCache(profile); AddProfileToCache(profile);
DoFinalInitLogging(profile); DoFinalInitLogging(profile);
#if defined(OS_WIN)
CreateDesktopShortcut(profile);
#endif
ProfileMetrics::LogNumberOfProfiles(this, ProfileMetrics::ADD_PROFILE_EVENT); ProfileMetrics::LogNumberOfProfiles(this, ProfileMetrics::ADD_PROFILE_EVENT);
content::NotificationService::current()->Notify( content::NotificationService::current()->Notify(
...@@ -732,12 +738,6 @@ Profile* ProfileManager::CreateProfileAsyncHelper(const FilePath& path, ...@@ -732,12 +738,6 @@ Profile* ProfileManager::CreateProfileAsyncHelper(const FilePath& path,
Profile::CREATE_MODE_ASYNCHRONOUS); Profile::CREATE_MODE_ASYNCHRONOUS);
} }
#if defined(OS_WIN)
ProfileShortcutManagerWin* ProfileManager::CreateShortcutManager() {
return new ProfileShortcutManagerWin();
}
#endif
void ProfileManager::OnProfileCreated(Profile* profile, void ProfileManager::OnProfileCreated(Profile* profile,
bool success, bool success,
bool is_new_profile) { bool is_new_profile) {
...@@ -838,16 +838,6 @@ ProfileInfoCache& ProfileManager::GetProfileInfoCache() { ...@@ -838,16 +838,6 @@ ProfileInfoCache& ProfileManager::GetProfileInfoCache() {
if (!profile_info_cache_.get()) { if (!profile_info_cache_.get()) {
profile_info_cache_.reset(new ProfileInfoCache( profile_info_cache_.reset(new ProfileInfoCache(
g_browser_process->local_state(), user_data_dir_)); g_browser_process->local_state(), user_data_dir_));
#if defined(OS_WIN)
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
ProfileShortcutManagerWin* shortcut_manager = CreateShortcutManager();
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (dist && dist->CanCreateDesktopShortcuts() && shortcut_manager &&
!command_line.HasSwitch(switches::kDisableDesktopShortcuts)) {
profile_shortcut_manager_.reset(shortcut_manager);
profile_info_cache_->AddObserver(profile_shortcut_manager_.get());
}
#endif
} }
return *profile_info_cache_.get(); return *profile_info_cache_.get();
} }
...@@ -877,28 +867,6 @@ void ProfileManager::AddProfileToCache(Profile* profile) { ...@@ -877,28 +867,6 @@ void ProfileManager::AddProfileToCache(Profile* profile) {
icon_index); icon_index);
} }
#if defined(OS_WIN)
void ProfileManager::CreateDesktopShortcut(Profile* profile) {
// TODO(sail): Disable creating new shortcuts for now.
return;
// Some distributions and tests cannot create desktop shortcuts, in which case
// profile_shortcut_manager_ will not be set.
if (!profile_shortcut_manager_.get())
return;
bool shortcut_created =
profile->GetPrefs()->GetBoolean(prefs::kProfileShortcutCreated);
if (!shortcut_created && GetNumberOfProfiles() > 1) {
profile_shortcut_manager_->AddProfileShortcut(profile->GetPath());
// We only ever create the shortcut for a profile once, so set a pref
// reminding us to skip this in the future.
profile->GetPrefs()->SetBoolean(prefs::kProfileShortcutCreated, true);
}
}
#endif
void ProfileManager::InitProfileUserPrefs(Profile* profile) { void ProfileManager::InitProfileUserPrefs(Profile* profile) {
ProfileInfoCache& cache = GetProfileInfoCache(); ProfileInfoCache& cache = GetProfileInfoCache();
...@@ -989,6 +957,10 @@ void ProfileManager::ScheduleProfileForDeletion(const FilePath& profile_dir) { ...@@ -989,6 +957,10 @@ void ProfileManager::ScheduleProfileForDeletion(const FilePath& profile_dir) {
QueueProfileDirectoryForDeletion(profile_dir); QueueProfileDirectoryForDeletion(profile_dir);
cache.DeleteProfileFromCache(profile_dir); cache.DeleteProfileFromCache(profile_dir);
// Delete possible shortcuts for this profile
if (profile_shortcut_manager_.get())
profile_shortcut_manager_->DeleteChromeDesktopShortcut(profile_dir);
ProfileMetrics::LogNumberOfProfiles(this, ProfileMetrics::LogNumberOfProfiles(this,
ProfileMetrics::DELETE_PROFILE_EVENT); ProfileMetrics::DELETE_PROFILE_EVENT);
} }
......
...@@ -19,15 +19,12 @@ ...@@ -19,15 +19,12 @@
#include "base/message_loop.h" #include "base/message_loop.h"
#include "base/threading/non_thread_safe.h" #include "base/threading/non_thread_safe.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_shortcut_manager.h"
#include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/startup/startup_types.h" #include "chrome/browser/ui/startup/startup_types.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
#if defined(OS_WIN)
#include "chrome/browser/profiles/profile_shortcut_manager_win.h"
#endif
class NewProfileLauncher; class NewProfileLauncher;
class ProfileInfoCache; class ProfileInfoCache;
...@@ -209,12 +206,6 @@ class ProfileManager : public base::NonThreadSafe, ...@@ -209,12 +206,6 @@ class ProfileManager : public base::NonThreadSafe,
virtual Profile* CreateProfileAsyncHelper(const FilePath& path, virtual Profile* CreateProfileAsyncHelper(const FilePath& path,
Delegate* delegate); Delegate* delegate);
#if defined(OS_WIN)
// Creates a shortcut manager. Override this to return a different shortcut
// manager or NULL to avoid creating shortcuts.
virtual ProfileShortcutManagerWin* CreateShortcutManager();
#endif
private: private:
friend class TestingProfileManager; friend class TestingProfileManager;
FRIEND_TEST_ALL_PREFIXES(ProfileManagerBrowserTest, DeleteAllProfiles); FRIEND_TEST_ALL_PREFIXES(ProfileManagerBrowserTest, DeleteAllProfiles);
...@@ -258,12 +249,6 @@ class ProfileManager : public base::NonThreadSafe, ...@@ -258,12 +249,6 @@ class ProfileManager : public base::NonThreadSafe,
// Adds |profile| to the profile info cache if it hasn't been added yet. // Adds |profile| to the profile info cache if it hasn't been added yet.
void AddProfileToCache(Profile* profile); void AddProfileToCache(Profile* profile);
#if defined(OS_WIN)
// Creates a profile desktop shortcut for |profile| if we are in multi
// profile mode and the shortcut has not been created before.
void CreateDesktopShortcut(Profile* profile);
#endif
// Initializes user prefs of |profile|. This includes profile name and // Initializes user prefs of |profile|. This includes profile name and
// avatar values // avatar values
void InitProfileUserPrefs(Profile* profile); void InitProfileUserPrefs(Profile* profile);
...@@ -306,11 +291,8 @@ class ProfileManager : public base::NonThreadSafe, ...@@ -306,11 +291,8 @@ class ProfileManager : public base::NonThreadSafe,
// if it has not been explicitly deleted. // if it has not been explicitly deleted.
scoped_ptr<ProfileInfoCache> profile_info_cache_; scoped_ptr<ProfileInfoCache> profile_info_cache_;
#if defined(OS_WIN) // Manages the process of creating, deleteing and updating Desktop shortcuts.
// Manages the creation, deletion, and renaming of Windows shortcuts by scoped_ptr<ProfileShortcutManager> profile_shortcut_manager_;
// observing the ProfileInfoCache.
scoped_ptr<ProfileShortcutManagerWin> profile_shortcut_manager_;
#endif
#if !defined(OS_ANDROID) #if !defined(OS_ANDROID)
class BrowserListObserver : public chrome::BrowserListObserver { class BrowserListObserver : public chrome::BrowserListObserver {
......
...@@ -77,13 +77,6 @@ class ProfileManager : public ::ProfileManagerWithoutInit { ...@@ -77,13 +77,6 @@ class ProfileManager : public ::ProfileManagerWithoutInit {
return new TestingProfile(path, this); return new TestingProfile(path, this);
} }
#if defined(OS_WIN)
virtual ProfileShortcutManagerWin* CreateShortcutManager() OVERRIDE {
// We should avoid creating shortcuts in these tests.
return NULL;
}
#endif
}; };
} // namespace testing } // namespace testing
...@@ -117,8 +110,9 @@ class ProfileManagerTest : public testing::Test { ...@@ -117,8 +110,9 @@ class ProfileManagerTest : public testing::Test {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
static_cast<TestingBrowserProcess*>(g_browser_process)->SetProfileManager( static_cast<TestingBrowserProcess*>(g_browser_process)->SetProfileManager(
new testing::ProfileManager(temp_dir_.path())); new testing::ProfileManager(temp_dir_.path()));
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
CommandLine *cl = CommandLine::ForCurrentProcess(); CommandLine* cl = CommandLine::ForCurrentProcess();
cl->AppendSwitch(switches::kTestType); cl->AppendSwitch(switches::kTestType);
#endif #endif
} }
...@@ -287,13 +281,16 @@ TEST_F(ProfileManagerTest, CreateProfileAsyncMultipleRequests) { ...@@ -287,13 +281,16 @@ TEST_F(ProfileManagerTest, CreateProfileAsyncMultipleRequests) {
profile_manager->CreateProfileAsync(dest_path, profile_manager->CreateProfileAsync(dest_path,
base::Bind(&MockObserver::OnProfileCreated, base::Bind(&MockObserver::OnProfileCreated,
base::Unretained(&mock_observer1)), string16(), string16()); base::Unretained(&mock_observer1)),
string16(), string16());
profile_manager->CreateProfileAsync(dest_path, profile_manager->CreateProfileAsync(dest_path,
base::Bind(&MockObserver::OnProfileCreated, base::Bind(&MockObserver::OnProfileCreated,
base::Unretained(&mock_observer2)), string16(), string16()); base::Unretained(&mock_observer2)),
string16(), string16());
profile_manager->CreateProfileAsync(dest_path, profile_manager->CreateProfileAsync(dest_path,
base::Bind(&MockObserver::OnProfileCreated, base::Bind(&MockObserver::OnProfileCreated,
base::Unretained(&mock_observer3)), string16(), string16()); base::Unretained(&mock_observer3)),
string16(), string16());
message_loop_.RunAllPending(); message_loop_.RunAllPending();
} }
......
// Copyright (c) 2012 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 "chrome/browser/profiles/profile_shortcut_manager.h"
ProfileShortcutManager::~ProfileShortcutManager() {
}
ProfileShortcutManager::ProfileShortcutManager() {
}
// Copyright (c) 2012 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.
#ifndef CHROME_BROWSER_PROFILES_PROFILE_SHORTCUT_MANAGER_H_
#define CHROME_BROWSER_PROFILES_PROFILE_SHORTCUT_MANAGER_H_
#include "base/file_path.h"
#include "ui/gfx/image/image.h"
class ProfileShortcutManager {
public:
virtual ~ProfileShortcutManager();
static bool IsFeatureEnabled();
static ProfileShortcutManager* Create();
virtual void CreateChromeDesktopShortcut(
const FilePath& profile_path, const string16& profile_name,
const gfx::Image& avatar_image) = 0;
virtual void DeleteChromeDesktopShortcut(const FilePath& profile_path) = 0;
protected:
ProfileShortcutManager();
private:
DISALLOW_COPY_AND_ASSIGN(ProfileShortcutManager);
};
#endif // CHROME_BROWSER_PROFILES_PROFILE_SHORTCUT_MANAGER_H_
// Copyright (c) 2012 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 "chrome/browser/profiles/profile_shortcut_manager.h"
// static
bool ProfileShortcutManager::IsFeatureEnabled() {
return false;
}
// static
ProfileShortcutManager* ProfileShortcutManager::Create() {
return NULL;
}
// Copyright (c) 2012 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 "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/scoped_temp_dir.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_shortcut_manager.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/test/base/testing_pref_service.h"
#include "content/public/test/test_browser_thread.h"
#include "grit/theme_resources.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/resource/resource_bundle.h"
using content::BrowserThread;
class ProfileShortcutManagerTest : public testing::Test {
protected:
ProfileShortcutManagerTest()
: file_thread_(BrowserThread::FILE, &message_loop_) {
}
virtual void SetUp() {
// Create a new temporary directory, and store the path
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
virtual void TearDown() {
message_loop_.RunAllPending();
}
// The path to temporary directory used to contain the test operations.
ScopedTempDir temp_dir_;
MessageLoopForUI message_loop_;
content::TestBrowserThread file_thread_;
};
TEST_F(ProfileShortcutManagerTest, DesktopShortcutsIconExists) {
// Profile shortcut manager will be NULL for non-windows platforms
ProfileShortcutManager* profile_shortcut_manager =
ProfileShortcutManager::Create();
if (!profile_shortcut_manager)
return;
FilePath dest_path = temp_dir_.path();
string16 profile_name = ASCIIToUTF16("My Profile");
gfx::Image& avatar = ResourceBundle::GetSharedInstance().
GetNativeImageNamed(IDR_PROFILE_AVATAR_0);
profile_shortcut_manager->CreateChromeDesktopShortcut(dest_path,
profile_name, avatar);
ASSERT_TRUE(file_util::PathExists(dest_path.Append(
(FILE_PATH_LITERAL("Google Profile.ico")))));
profile_shortcut_manager->DeleteChromeDesktopShortcut(dest_path);
// TODO(hallielaine): Verify shortcut deletion
}
TEST_F(ProfileShortcutManagerTest, DesktopShortcutsLnk) {
// Profile shortcut manager will be NULL for non-windows platforms
ProfileShortcutManager* profile_shortcut_manager =
ProfileShortcutManager::Create();
if (!profile_shortcut_manager)
return;
FilePath dest_path = temp_dir_.path();
dest_path = dest_path.Append(FILE_PATH_LITERAL("New Profile 1"));
gfx::Image& avatar = ResourceBundle::GetSharedInstance().
GetNativeImageNamed(IDR_PROFILE_AVATAR_0);
profile_shortcut_manager->CreateChromeDesktopShortcut(dest_path,
ASCIIToUTF16("My Profile"), avatar);
FilePath exe_path;
ASSERT_TRUE(PathService::Get(base::FILE_EXE, &exe_path));
FilePath shortcut;
string16 shortcut_name;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
// Get the desktop path of the current user
ShellUtil::GetDesktopPath(false, &shortcut);
// Get the name of the shortcut with profile attached
ShellUtil::GetChromeShortcutName(dist, false, ASCIIToUTF16("My Profile"),
&shortcut_name);
shortcut = shortcut.Append(shortcut_name);
EXPECT_EQ(ShellUtil::VERIFY_SHORTCUT_SUCCESS,
ShellUtil::VerifyChromeShortcut(exe_path.value(),
shortcut.value(), dist->GetAppDescription(), 0));
profile_shortcut_manager->DeleteChromeDesktopShortcut(dest_path);
}
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "chrome/browser/profiles/profile_shortcut_manager_win.h" #include "chrome/browser/profiles/profile_shortcut_manager.h"
#include <map>
#include "base/bind.h" #include "base/bind.h"
#include "base/file_path.h" #include "base/command_line.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/stringprintf.h" #include "base/stringprintf.h"
...@@ -29,7 +31,6 @@ ...@@ -29,7 +31,6 @@
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h" #include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/icon_util.h" #include "ui/gfx/icon_util.h"
#include "ui/gfx/image/image.h"
using content::BrowserThread; using content::BrowserThread;
...@@ -40,22 +41,6 @@ const int kProfileAvatarShortcutBadgeWidth = 28; ...@@ -40,22 +41,6 @@ const int kProfileAvatarShortcutBadgeWidth = 28;
const int kProfileAvatarShortcutBadgeHeight = 28; const int kProfileAvatarShortcutBadgeHeight = 28;
const int kShortcutIconSize = 48; const int kShortcutIconSize = 48;
// Creates the argument to pass to the Windows executable that launches Chrome
// with the profile in |profile_base_dir|.
// For example: --profile-directory="Profile 2"
string16 CreateProfileShortcutSwitch(const string16& profile_base_dir) {
return base::StringPrintf(L"--%ls=\"%ls\"",
ASCIIToUTF16(switches::kProfileDirectory).c_str(),
profile_base_dir.c_str());
}
// Wrap a ShellUtil function that returns a bool so it can be posted in a
// task to the FILE thread.
void CallShellUtilBoolFunction(
const base::Callback<bool(void)>& bool_function) {
bool_function.Run();
}
// Creates a desktop shortcut icon file (.ico) on the disk for a given profile, // Creates a desktop shortcut icon file (.ico) on the disk for a given profile,
// badging the browser distribution icon with the profile avatar. // badging the browser distribution icon with the profile avatar.
// |profile_base_dir| is the base directory (and key) of the profile. Returns // |profile_base_dir| is the base directory (and key) of the profile. Returns
...@@ -73,7 +58,7 @@ FilePath CreateChromeDesktopShortcutIconForProfile( ...@@ -73,7 +58,7 @@ FilePath CreateChromeDesktopShortcutIconForProfile(
if (!app_icon_bitmap.get()) if (!app_icon_bitmap.get())
return FilePath(); return FilePath();
// TODO(stevet): Share this chunk of code with // TODO(hallielaine): Share this chunk of code with
// avatar_menu_button::DrawTaskBarDecoration. // avatar_menu_button::DrawTaskBarDecoration.
const SkBitmap* source_bitmap = NULL; const SkBitmap* source_bitmap = NULL;
SkBitmap squarer_bitmap; SkBitmap squarer_bitmap;
...@@ -117,106 +102,72 @@ FilePath CreateChromeDesktopShortcutIconForProfile( ...@@ -117,106 +102,72 @@ FilePath CreateChromeDesktopShortcutIconForProfile(
return icon_path; return icon_path;
} }
// Creates a desktop shortcut to open Chrome with the given profile name and string16 CreateProfileShortcutFlags(const FilePath& profile_path) {
// base directory. Iff |create|, create shortcut if it doesn't already exist. return base::StringPrintf(L"--%ls=\"%ls\"",
// Must be called on the FILE thread. ASCIIToUTF16(switches::kProfileDirectory).c_str(),
void CreateChromeDesktopShortcutForProfile( profile_path.BaseName().value().c_str());
const string16& profile_name,
const string16& profile_base_dir,
const FilePath& profile_path,
const gfx::Image* avatar_image,
bool create) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
FilePath chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe))
return;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
string16 description;
if (!dist)
return;
else
description = WideToUTF16(dist->GetAppDescription());
const string16& directory = CreateProfileShortcutSwitch(profile_base_dir);
FilePath icon_path = avatar_image ?
CreateChromeDesktopShortcutIconForProfile(profile_path, *avatar_image) :
FilePath();
ShellUtil::CreateChromeDesktopShortcut(
dist,
chrome_exe.value(),
description,
profile_name,
directory,
icon_path.empty() ? chrome_exe.value() : icon_path.value(),
icon_path.empty() ? dist->GetIconIndex() : 0,
ShellUtil::CURRENT_USER,
create ? ShellUtil::SHORTCUT_CREATE_ALWAYS :
ShellUtil::SHORTCUT_NO_OPTIONS);
} }
// Renames an existing Chrome desktop profile shortcut. Must be called on the // Wrap a ShellUtil function that returns a bool so it can be posted in a
// FILE thread. // task to the FILE thread.
void RenameChromeDesktopShortcutForProfile( void CallShellUtilBoolFunction(
const string16& old_shortcut, const base::Callback<bool(void)>& bool_function) {
const string16& new_shortcut) { bool_function.Run();
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
FilePath shortcut_path;
if (ShellUtil::GetDesktopPath(false, // User's directory instead of system.
&shortcut_path)) {
FilePath old_profile = shortcut_path.Append(old_shortcut);
FilePath new_profile = shortcut_path.Append(new_shortcut);
file_util::Move(old_profile, new_profile);
}
} }
// Updates the arguments to a Chrome desktop shortcut for a profile. Must be } // namespace
// called on the FILE thread.
void UpdateChromeDesktopShortcutForProfile(
const string16& shortcut,
const string16& arguments,
const FilePath& profile_path,
const gfx::Image* avatar_image) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
FilePath shortcut_path;
if (!ShellUtil::GetDesktopPath(false, &shortcut_path))
return;
shortcut_path = shortcut_path.Append(shortcut); class ProfileShortcutManagerWin : public ProfileShortcutManager {
FilePath chrome_exe; public:
if (!PathService::Get(base::FILE_EXE, &chrome_exe)) ProfileShortcutManagerWin();
return; virtual ~ProfileShortcutManagerWin();
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
string16 description; virtual void CreateChromeDesktopShortcut(
if (!dist) const FilePath& profile_path, const string16& profile_name,
return; const gfx::Image& avatar_image) OVERRIDE;
else virtual void DeleteChromeDesktopShortcut(const FilePath& profile_path)
description = WideToUTF16(dist->GetAppDescription()); OVERRIDE;
FilePath icon_path = avatar_image ?
CreateChromeDesktopShortcutIconForProfile(profile_path, *avatar_image) : private:
FilePath(); struct ProfileShortcutInfo {
string16 flags;
string16 profile_name;
gfx::Image avatar_image;
ProfileShortcutInfo()
: flags(string16()),
profile_name(string16()),
avatar_image(gfx::Image()) {
}
ShellUtil::UpdateChromeShortcut( ProfileShortcutInfo(
dist, string16 p_flags,
chrome_exe.value(), string16 p_profile_name,
shortcut_path.value(), gfx::Image p_avatar_image)
arguments, : flags(p_flags),
description, profile_name(p_profile_name),
icon_path.empty() ? chrome_exe.value() : icon_path.value(), avatar_image(p_avatar_image) {
icon_path.empty() ? dist->GetIconIndex() : 0, }
ShellUtil::SHORTCUT_NO_OPTIONS); };
}
void DeleteAutoLaunchValueForProfile( // TODO(hallielaine): Repopulate this map on chrome session startup
const FilePath& profile_path) { typedef std::map<FilePath, ProfileShortcutInfo> ProfileShortcutsMap;
if (auto_launch_util::AutoStartRequested(profile_path.BaseName().value(), ProfileShortcutsMap profile_shortcuts_;
true, // Window requested. };
FilePath())) {
auto_launch_util::DisableForegroundStartAtLogin( // static
profile_path.BaseName().value()); bool ProfileShortcutManager::IsFeatureEnabled() {
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kProfileDesktopShortcuts)) {
return true;
} }
return false;
} }
} // namespace // static
ProfileShortcutManager* ProfileShortcutManager::Create() {
return new ProfileShortcutManagerWin();
}
ProfileShortcutManagerWin::ProfileShortcutManagerWin() { ProfileShortcutManagerWin::ProfileShortcutManagerWin() {
} }
...@@ -224,208 +175,55 @@ ProfileShortcutManagerWin::ProfileShortcutManagerWin() { ...@@ -224,208 +175,55 @@ ProfileShortcutManagerWin::ProfileShortcutManagerWin() {
ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { ProfileShortcutManagerWin::~ProfileShortcutManagerWin() {
} }
void ProfileShortcutManagerWin::AddProfileShortcut( void ProfileShortcutManagerWin::CreateChromeDesktopShortcut(
const FilePath& profile_path) { const FilePath& profile_path, const string16& profile_name,
ProfileInfoCache& cache = const gfx::Image& avatar_image) {
g_browser_process->profile_manager()->GetProfileInfoCache(); FilePath shortcut_icon = CreateChromeDesktopShortcutIconForProfile(
size_t index = cache.GetIndexOfProfileWithPath(profile_path); profile_path, avatar_image);
if (index == std::string::npos) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
FilePath chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe))
return; return;
// Launch task to add shortcut to desktop on Windows. If this is the very
// first profile created, don't add the user name to the shortcut.
// TODO(mirandac): respect master_preferences choice to create no shortcuts
// (see http://crbug.com/104463)
if (g_browser_process->profile_manager()->GetNumberOfProfiles() > 1) {
{
// We make a copy of the Image to ensure that the underlying image data is
// AddRef'd, in case the original copy gets deleted.
gfx::Image* avatar_copy =
new gfx::Image(cache.GetAvatarIconOfProfileAtIndex(index));
string16 profile_name = cache.GetNameOfProfileAtIndex(index);
string16 profile_base_dir = profile_path.BaseName().value();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&CreateChromeDesktopShortcutForProfile,
profile_name, profile_base_dir, profile_path,
base::Owned(avatar_copy), true));
}
// If this is the second existing multi-user account created, change the
// original shortcut use the first profile's details (name, badge,
// argument).
if (cache.GetNumberOfProfiles() == 2) {
// Get the index of the first profile, based on the index of the second
// profile. It's either 0 or 1, whichever the second profile isn't.
size_t first_index = 0;
if (cache.GetIndexOfProfileWithPath(profile_path) == 0)
first_index = 1;
string16 first_name = cache.GetNameOfProfileAtIndex(first_index);
BrowserDistribution* dist = BrowserDistribution::GetDistribution(); BrowserDistribution* dist = BrowserDistribution::GetDistribution();
string16 description;
if (!dist)
return;
description = WideToUTF16(dist->GetAppDescription());
string16 old_shortcut; // Add the profile to the map if it doesn't exist already
string16 new_shortcut; if (!profile_shortcuts_.count(profile_path)) {
if (ShellUtil::GetChromeShortcutName(dist, false, L"", &old_shortcut) && string16 flags = CreateProfileShortcutFlags(profile_path);
ShellUtil::GetChromeShortcutName(dist, false, first_name, profile_shortcuts_.insert(std::make_pair(profile_path,
&new_shortcut)) { ProfileShortcutInfo(flags, profile_name,
// Update doesn't allow changing the target, so rename first. avatar_image)));
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&RenameChromeDesktopShortcutForProfile,
old_shortcut, new_shortcut));
// Fetch the avatar for the first profile and make a copy of the Image
// to ensure that the underlying image data is AddRef'd, in case the
// original copy is deleted.
gfx::Image& first_avatar =
ResourceBundle::GetSharedInstance().GetNativeImageNamed(
ProfileInfoCache::GetDefaultAvatarIconResourceIDAtIndex(
cache.GetAvatarIconIndexOfProfileAtIndex(first_index)));
gfx::Image* first_avatar_copy = new gfx::Image(first_avatar);
FilePath first_path = cache.GetPathOfProfileAtIndex(first_index);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&UpdateChromeDesktopShortcutForProfile,
new_shortcut,
CreateProfileShortcutSwitch(UTF8ToUTF16(
first_path.BaseName().MaybeAsASCII())),
first_path,
base::Owned(first_avatar_copy)));
}
} }
} else { // Only one profile, so create original shortcut, with no avatar.
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&CreateChromeDesktopShortcutForProfile,
string16(), string16(), FilePath(),
static_cast<gfx::Image*>(NULL), true));
}
}
void ProfileShortcutManagerWin::OnProfileAdded( ShellUtil::CreateChromeDesktopShortcut(
const FilePath& profile_path) { dist,
chrome_exe.value(),
description,
profile_shortcuts_[profile_path].profile_name,
profile_shortcuts_[profile_path].flags,
shortcut_icon.empty() ? chrome_exe.value() : shortcut_icon.value(),
shortcut_icon.empty() ? dist->GetIconIndex() : 0,
ShellUtil::CURRENT_USER,
ShellUtil::SHORTCUT_CREATE_ALWAYS);
} }
void ProfileShortcutManagerWin::OnProfileWillBeRemoved( void ProfileShortcutManagerWin::DeleteChromeDesktopShortcut(
const FilePath& profile_path) { const FilePath& profile_path) {
ProfileInfoCache& cache =
g_browser_process->profile_manager()->GetProfileInfoCache();
size_t index = cache.GetIndexOfProfileWithPath(profile_path);
if (index == std::string::npos)
return;
string16 profile_name = cache.GetNameOfProfileAtIndex(index);
BrowserDistribution* dist = BrowserDistribution::GetDistribution(); BrowserDistribution* dist = BrowserDistribution::GetDistribution();
string16 shortcut; string16 shortcut;
if (ShellUtil::GetChromeShortcutName(dist, false, profile_name, &shortcut)) { // If we can find the shortcut, delete it
std::vector<string16> shortcuts(1, shortcut); if (ShellUtil::GetChromeShortcutName(dist, false,
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, profile_shortcuts_[profile_path].profile_name, &shortcut)) {
base::Bind(&CallShellUtilBoolFunction, std::vector<string16> appended_names(1, shortcut);
base::Bind(
&ShellUtil::RemoveChromeDesktopShortcutsWithAppendedNames,
shortcuts)));
}
}
void ProfileShortcutManagerWin::OnProfileWasRemoved(
const FilePath& profile_path,
const string16& profile_name) {
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE, BrowserThread::FILE, FROM_HERE,
base::Bind(&DeleteAutoLaunchValueForProfile, profile_path)); base::Bind(&CallShellUtilBoolFunction, base::Bind(
&ShellUtil::RemoveChromeDesktopShortcutsWithAppendedNames,
// If there is one profile left, we want to remove the badge and name from it. appended_names)));
ProfileInfoCache& cache = profile_shortcuts_.erase(profile_path);
g_browser_process->profile_manager()->GetProfileInfoCache();
if (cache.GetNumberOfProfiles() != 1)
return;
FilePath last_profile_path = cache.GetPathOfProfileAtIndex(0);
string16 old_shortcut;
string16 new_shortcut;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
if (ShellUtil::GetChromeShortcutName(
dist, false, cache.GetNameOfProfileAtIndex(0), &old_shortcut) &&
ShellUtil::GetChromeShortcutName(
dist, false, L"", &new_shortcut)) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&RenameChromeDesktopShortcutForProfile,
old_shortcut,
new_shortcut));
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&UpdateChromeDesktopShortcutForProfile,
new_shortcut,
CreateProfileShortcutSwitch(UTF8ToUTF16(
last_profile_path.BaseName().MaybeAsASCII())),
last_profile_path,
static_cast<gfx::Image*>(NULL)));
}
}
void ProfileShortcutManagerWin::OnProfileNameChanged(
const FilePath& profile_path,
const string16& old_profile_name) {
// Launch task to change name of desktop shortcut on Windows.
// TODO(mirandac): respect master_preferences choice to create no shortcuts
// (see http://crbug.com/104463)
ProfileInfoCache& cache =
g_browser_process->profile_manager()->GetProfileInfoCache();
size_t index = cache.GetIndexOfProfileWithPath(profile_path);
if (index == std::string::npos)
return;
string16 new_profile_name = cache.GetNameOfProfileAtIndex(index);
string16 old_shortcut;
string16 new_shortcut;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
if (ShellUtil::GetChromeShortcutName(
dist, false, old_profile_name, &old_shortcut) &&
ShellUtil::GetChromeShortcutName(
dist, false, new_profile_name, &new_shortcut)) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&RenameChromeDesktopShortcutForProfile,
old_shortcut,
new_shortcut));
}
}
void ProfileShortcutManagerWin::OnProfileAvatarChanged(
const FilePath& profile_path) {
ProfileInfoCache& cache =
g_browser_process->profile_manager()->GetProfileInfoCache();
size_t index = cache.GetIndexOfProfileWithPath(profile_path);
if (index == std::string::npos)
return;
string16 profile_name = cache.GetNameOfProfileAtIndex(index);
string16 profile_base_dir =
UTF8ToUTF16(profile_path.BaseName().MaybeAsASCII());
const gfx::Image& avatar_image = cache.GetAvatarIconOfProfileAtIndex(index);
// Launch task to change the icon of the desktop shortcut on windows.
string16 new_shortcut;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
if (ShellUtil::GetChromeShortcutName(dist, false, profile_name,
&new_shortcut)) {
// We make a copy of the Image to ensure that the underlying image data is
// AddRef'd, in case the original copy gets deleted.
gfx::Image* avatar_copy = new gfx::Image(avatar_image);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&UpdateChromeDesktopShortcutForProfile,
new_shortcut,
CreateProfileShortcutSwitch(profile_base_dir),
profile_path,
base::Owned(avatar_copy)));
} }
} }
// static
std::vector<string16> ProfileShortcutManagerWin::GenerateShortcutsFromProfiles(
const std::vector<string16>& profile_names) {
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
std::vector<string16> shortcuts;
shortcuts.reserve(profile_names.size());
for (std::vector<string16>::const_iterator it = profile_names.begin();
it != profile_names.end();
++it) {
string16 shortcut;
if (ShellUtil::GetChromeShortcutName(dist, false, *it, &shortcut))
shortcuts.push_back(shortcut);
}
return shortcuts;
}
...@@ -1989,8 +1989,10 @@ ...@@ -1989,8 +1989,10 @@
'browser/profiles/profile_manager.h', 'browser/profiles/profile_manager.h',
'browser/profiles/profile_metrics.cc', 'browser/profiles/profile_metrics.cc',
'browser/profiles/profile_metrics.h', 'browser/profiles/profile_metrics.h',
'browser/profiles/profile_shortcut_manager.h',
'browser/profiles/profile_shortcut_manager.cc',
'browser/profiles/profile_shortcut_manager_stub.cc',
'browser/profiles/profile_shortcut_manager_win.cc', 'browser/profiles/profile_shortcut_manager_win.cc',
'browser/profiles/profile_shortcut_manager_win.h',
'browser/profiles/refcounted_profile_keyed_service.h', 'browser/profiles/refcounted_profile_keyed_service.h',
'browser/profiles/refcounted_profile_keyed_service.cc', 'browser/profiles/refcounted_profile_keyed_service.cc',
'browser/profiles/refcounted_profile_keyed_service_factory.h', 'browser/profiles/refcounted_profile_keyed_service_factory.h',
...@@ -5259,6 +5261,7 @@ ...@@ -5259,6 +5261,7 @@
'browser/importer/nss_decryptor_system_nss.cc', 'browser/importer/nss_decryptor_system_nss.cc',
'browser/importer/nss_decryptor_system_nss.h', 'browser/importer/nss_decryptor_system_nss.h',
'browser/lifetime/application_lifetime_stub.cc', 'browser/lifetime/application_lifetime_stub.cc',
'browser/profiles/profile_shortcut_manager_stub.cc',
'browser/ui/certificate_dialogs.cc', 'browser/ui/certificate_dialogs.cc',
'browser/ui/certificate_dialogs.h', 'browser/ui/certificate_dialogs.h',
'browser/ui/crypto_module_password_dialog.cc', 'browser/ui/crypto_module_password_dialog.cc',
......
...@@ -1505,6 +1505,7 @@ ...@@ -1505,6 +1505,7 @@
'browser/profiles/profile_info_cache_unittest.h', 'browser/profiles/profile_info_cache_unittest.h',
'browser/profiles/profile_info_util_unittest.cc', 'browser/profiles/profile_info_util_unittest.cc',
'browser/profiles/profile_manager_unittest.cc', 'browser/profiles/profile_manager_unittest.cc',
'browser/profiles/profile_shortcut_manager_unittest_win.cc',
'browser/protector/composite_settings_change_unittest.cc', 'browser/protector/composite_settings_change_unittest.cc',
'browser/protector/homepage_change_unittest.cc', 'browser/protector/homepage_change_unittest.cc',
'browser/protector/prefs_backup_invalid_change_unittest.cc', 'browser/protector/prefs_backup_invalid_change_unittest.cc',
......
...@@ -1074,6 +1074,9 @@ const char kProductVersion[] = "product-version"; ...@@ -1074,6 +1074,9 @@ const char kProductVersion[] = "product-version";
// Selects directory of profile to associate with the first browser launched. // Selects directory of profile to associate with the first browser launched.
const char kProfileDirectory[] = "profile-directory"; const char kProfileDirectory[] = "profile-directory";
// Enables the Windows profile desktop shortcuts feature.
const char kProfileDesktopShortcuts[] = "enable-profile-desktop-shortcuts";
// Starts the sampling based profiler for the browser process at startup. This // Starts the sampling based profiler for the browser process at startup. This
// will only work if chrome has been built with the gyp variable profiling=1. // will only work if chrome has been built with the gyp variable profiling=1.
// The output will go to the value of kProfilingFile. // The output will go to the value of kProfilingFile.
......
...@@ -288,6 +288,7 @@ extern const char kPrerenderModeSwitchValueEnabled[]; ...@@ -288,6 +288,7 @@ extern const char kPrerenderModeSwitchValueEnabled[];
extern const char kPrerenderModeSwitchValuePrefetchOnly[]; extern const char kPrerenderModeSwitchValuePrefetchOnly[];
extern const char kPrintSettingsReset[]; extern const char kPrintSettingsReset[];
extern const char kProductVersion[]; extern const char kProductVersion[];
extern const char kProfileDesktopShortcuts[];
extern const char kProfileDirectory[]; extern const char kProfileDirectory[];
extern const char kProfilingAtStart[]; extern const char kProfilingAtStart[];
extern const char kProfilingFile[]; extern const char kProfilingFile[];
......
...@@ -24,13 +24,6 @@ class ProfileManager : public ::ProfileManager { ...@@ -24,13 +24,6 @@ class ProfileManager : public ::ProfileManager {
virtual Profile* CreateProfileHelper(const FilePath& file_path) OVERRIDE { virtual Profile* CreateProfileHelper(const FilePath& file_path) OVERRIDE {
return new TestingProfile(file_path); return new TestingProfile(file_path);
} }
#if defined(OS_WIN)
virtual ProfileShortcutManagerWin* CreateShortcutManager() OVERRIDE {
// We should avoid creating shortcuts in these tests.
return NULL;
}
#endif
}; };
} // namespace testing } // namespace testing
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment