Commit 4479c603 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

MacPWAs: Fix duplicate bundle creation

The root cause of this bug is that the call to
LSCopyApplicationURLsForBundleIdentifier that is made in
WebAppShortcutCreator::GetAppBundlesByIdUnsorted will not necessarily
find app shims that were created just moments ago (likely there is an
asynchronous indexing that is going on).

To fix this, make WebAppShortcutCreator::GetAppBundlesByIdUnsorted read
through all of the app shims in the path under ~/Applications where
app shims are created, and see if it finds an application that matches
the one we are looking for, based on its Info.plist.

Rather than add another place where Info.plists are manually parsed,
merge all code that reads Info.plists from app bundles, and put all
of the accessors for this data in a single BundleInfoPlist class.
Note that some of the moved code is not well understood by this
author (in particular, IsForCurrentUserDataDir and GetFullProfilePath).

This BundleInfoPlist will be used more extensively in the updated code
to delete zombie bundles and to provide more robust renaming.

Bug: 937703
Change-Id: Ia292840debd9c74024d707f784dceab78443a536
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1500851
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarTrent Apted <tapted@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638225}
parent b6619d81
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <stdint.h> #include <stdint.h>
#include <algorithm> #include <algorithm>
#include <list>
#include <map> #include <map>
#include <utility> #include <utility>
...@@ -203,43 +204,123 @@ NSString* GetPlistPath(const base::FilePath& bundle_path) { ...@@ -203,43 +204,123 @@ NSString* GetPlistPath(const base::FilePath& bundle_path) {
bundle_path.Append("Contents").Append("Info.plist")); bundle_path.Append("Contents").Append("Info.plist"));
} }
NSMutableDictionary* ReadPlist(NSString* plist_path) { // Data and helpers for an Info.plist under a given bundle path.
return [NSMutableDictionary dictionaryWithContentsOfFile:plist_path]; class BundleInfoPlist {
} public:
// Retrieve info from all app shims found in |apps_path|.
bool HasExistingExtensionShimForDifferentProfile( static std::list<BundleInfoPlist> GetAllInPath(
const base::FilePath& destination_directory, const base::FilePath& apps_path,
const std::string& extension_id, bool recursive) {
const base::FilePath& profile_dir) { std::list<BundleInfoPlist> bundles;
// Check if there any any other shims for the same extension. base::FileEnumerator enumerator(apps_path, recursive,
base::FileEnumerator enumerator(destination_directory, false /* recursive */,
base::FileEnumerator::DIRECTORIES); base::FileEnumerator::DIRECTORIES);
for (base::FilePath shim_path = enumerator.Next(); !shim_path.empty(); for (base::FilePath shim_path = enumerator.Next(); !shim_path.empty();
shim_path = enumerator.Next()) { shim_path = enumerator.Next()) {
NSDictionary* plist = ReadPlist(GetPlistPath(shim_path)); bundles.emplace_back(shim_path);
std::string plist_extension_id = base::SysNSStringToUTF8( }
[plist valueForKey:app_mode::kCrAppModeShortcutIDKey]); return bundles;
base::FilePath plist_profile_dir(base::SysNSStringToUTF8(
[plist valueForKey:app_mode::kCrAppModeProfileDirKey]));
if (plist_extension_id == extension_id && plist_profile_dir != profile_dir)
return true;
} }
return false; // Retrieve info from the specified app shim in |bundle_path|.
} explicit BundleInfoPlist(const base::FilePath& bundle_path)
: bundle_path_(bundle_path) {
NSString* plist_path = GetPlistPath(bundle_path_);
plist_.reset([NSDictionary dictionaryWithContentsOfFile:plist_path],
base::scoped_policy::RETAIN);
}
~BundleInfoPlist() = default;
// Takes the path to an app bundle and checks that the CrAppModeUserDataDir in const base::FilePath& bundle_path() const { return bundle_path_; }
// the Info.plist starts with the current user_data_dir. This uses starts with
// instead of equals because the CrAppModeUserDataDir could be the user_data_dir // Checks that the CrAppModeUserDataDir in the Info.plist is, or is a subpath
// or the |app_data_dir_|. // of the current user_data_dir. This uses starts with instead of equals
bool HasSameUserDataDir(const base::FilePath& bundle_path) { // because the CrAppModeUserDataDir could be the user_data_dir or the
NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); // |app_data_dir_|.
bool IsForCurrentUserDataDir() const {
base::FilePath user_data_dir; base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
DCHECK(!user_data_dir.empty()); DCHECK(!user_data_dir.empty());
return base::StartsWith(base::SysNSStringToUTF8([plist return base::StartsWith(
valueForKey:app_mode::kCrAppModeUserDataDirKey]), base::SysNSStringToUTF8(
[plist_ valueForKey:app_mode::kCrAppModeUserDataDirKey]),
user_data_dir.value(), base::CompareCase::SENSITIVE); user_data_dir.value(), base::CompareCase::SENSITIVE);
}
// Checks if kCrAppModeProfileDirKey corresponds to the specified profile
// path.
bool IsForProfile(const base::FilePath& test_profile_path) const {
std::string profile_path = base::SysNSStringToUTF8(
[plist_ valueForKey:app_mode::kCrAppModeProfileDirKey]);
return profile_path == test_profile_path.BaseName().value();
}
// Return the full profile path (as a subpath of the user_data_dir).
base::FilePath GetFullProfilePath() const {
// Figure out the profile_path. Since the user_data_dir could contain the
// path to the web app data dir.
base::FilePath user_data_dir = base::mac::NSStringToFilePath(
[plist_ valueForKey:app_mode::kCrAppModeUserDataDirKey]);
base::FilePath profile_base_name = base::mac::NSStringToFilePath(
[plist_ valueForKey:app_mode::kCrAppModeProfileDirKey]);
if (user_data_dir.DirName().DirName().BaseName() == profile_base_name)
return user_data_dir.DirName().DirName();
return user_data_dir.Append(profile_base_name);
}
std::string GetExtensionId() const {
return base::SysNSStringToUTF8(
[plist_ valueForKey:app_mode::kCrAppModeShortcutIDKey]);
}
std::string GetProfileName() const {
return base::SysNSStringToUTF8(
[plist_ valueForKey:app_mode::kCrAppModeProfileNameKey]);
}
GURL GetURL() const {
return GURL(base::SysNSStringToUTF8(
[plist_ valueForKey:app_mode::kCrAppModeShortcutURLKey]));
}
base::string16 GetTitle() const {
return base::SysNSStringToUTF16(
[plist_ valueForKey:app_mode::kCrAppModeShortcutNameKey]);
}
base::Version GetVersion() const {
NSString* version_string =
[plist_ valueForKey:app_mode::kCrBundleVersionKey];
if (!version_string) {
// Older bundles have the Chrome version in the following key.
version_string =
[plist_ valueForKey:app_mode::kCFBundleShortVersionStringKey];
}
return base::Version(base::SysNSStringToUTF8(version_string));
}
std::string GetBundleId() const {
return base::SysNSStringToUTF8(
[plist_ valueForKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)]);
}
private:
// The path of the app bundle from this this info was read.
base::FilePath bundle_path_;
// Data read from the Info.plist.
base::scoped_nsobject<NSDictionary> plist_;
DISALLOW_COPY_AND_ASSIGN(BundleInfoPlist);
};
bool HasExistingExtensionShimForDifferentProfile(
const base::FilePath& destination_directory,
const std::string& extension_id,
const base::FilePath& profile_dir) {
std::list<BundleInfoPlist> bundles_info = BundleInfoPlist::GetAllInPath(
destination_directory, false /* recursive */);
for (const auto& info : bundles_info) {
if (info.GetExtensionId() == extension_id &&
!info.IsForProfile(profile_dir)) {
return true;
}
}
return false;
} }
void LaunchShimOnFileThread(web_app::LaunchShimUpdateBehavior update_behavior, void LaunchShimOnFileThread(web_app::LaunchShimUpdateBehavior update_behavior,
...@@ -485,57 +566,17 @@ bool UpdateAppShortcutsSubdirLocalizedName( ...@@ -485,57 +566,17 @@ bool UpdateAppShortcutsSubdirLocalizedName(
return true; return true;
} }
bool IsShimForProfile(const base::FilePath& bundle_path,
const std::string& profile_base_name) {
NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path));
std::string profile_dir = base::SysNSStringToUTF8(
[plist valueForKey:app_mode::kCrAppModeProfileDirKey]);
return profile_dir == profile_base_name;
}
std::vector<base::FilePath> GetAllAppBundlesInPath(
const base::FilePath& internal_shortcut_path,
const std::string& profile_base_name) {
std::vector<base::FilePath> bundle_paths;
base::FileEnumerator enumerator(internal_shortcut_path, true /* recursive */,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath bundle_path = enumerator.Next(); !bundle_path.empty();
bundle_path = enumerator.Next()) {
if (IsShimForProfile(bundle_path, profile_base_name))
bundle_paths.push_back(bundle_path);
}
return bundle_paths;
}
std::unique_ptr<web_app::ShortcutInfo> BuildShortcutInfoFromBundle( std::unique_ptr<web_app::ShortcutInfo> BuildShortcutInfoFromBundle(
const base::FilePath& bundle_path) { const base::FilePath& bundle_path) {
NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); BundleInfoPlist bundle_info(bundle_path);
std::unique_ptr<web_app::ShortcutInfo> shortcut_info( std::unique_ptr<web_app::ShortcutInfo> shortcut_info(
new web_app::ShortcutInfo); new web_app::ShortcutInfo);
shortcut_info->extension_id = base::SysNSStringToUTF8( shortcut_info->extension_id = bundle_info.GetExtensionId();
[plist valueForKey:app_mode::kCrAppModeShortcutIDKey]);
shortcut_info->is_platform_app = true; shortcut_info->is_platform_app = true;
shortcut_info->url = GURL(base::SysNSStringToUTF8( shortcut_info->url = bundle_info.GetURL();
[plist valueForKey:app_mode::kCrAppModeShortcutURLKey])); shortcut_info->title = bundle_info.GetTitle();
shortcut_info->title = base::SysNSStringToUTF16( shortcut_info->profile_name = bundle_info.GetProfileName();
[plist valueForKey:app_mode::kCrAppModeShortcutNameKey]); shortcut_info->profile_path = bundle_info.GetFullProfilePath();
shortcut_info->profile_name = base::SysNSStringToUTF8(
[plist valueForKey:app_mode::kCrAppModeProfileNameKey]);
// Figure out the profile_path. Since the user_data_dir could contain the
// path to the web app data dir.
base::FilePath user_data_dir = base::mac::NSStringToFilePath(
[plist valueForKey:app_mode::kCrAppModeUserDataDirKey]);
base::FilePath profile_base_name = base::mac::NSStringToFilePath(
[plist valueForKey:app_mode::kCrAppModeProfileDirKey]);
if (user_data_dir.DirName().DirName().BaseName() == profile_base_name)
shortcut_info->profile_path = user_data_dir.DirName().DirName();
else
shortcut_info->profile_path = user_data_dir.Append(profile_base_name);
return shortcut_info; return shortcut_info;
} }
...@@ -545,14 +586,7 @@ namespace web_app { ...@@ -545,14 +586,7 @@ namespace web_app {
std::unique_ptr<web_app::ShortcutInfo> RecordAppShimErrorAndBuildShortcutInfo( std::unique_ptr<web_app::ShortcutInfo> RecordAppShimErrorAndBuildShortcutInfo(
const base::FilePath& bundle_path) { const base::FilePath& bundle_path) {
NSDictionary* plist = ReadPlist(GetPlistPath(bundle_path)); base::Version full_version = BundleInfoPlist(bundle_path).GetVersion();
NSString* version_string = [plist valueForKey:app_mode::kCrBundleVersionKey];
if (!version_string) {
// Older bundles have the Chrome version in the following key.
version_string =
[plist valueForKey:app_mode::kCFBundleShortVersionStringKey];
}
base::Version full_version(base::SysNSStringToUTF8(version_string));
uint32_t major_version = 0; uint32_t major_version = 0;
if (full_version.IsValid()) if (full_version.IsValid())
major_version = full_version.components()[0]; major_version = full_version.components()[0];
...@@ -803,7 +837,8 @@ bool WebAppShortcutCreator::UpdatePlist(const base::FilePath& app_path) const { ...@@ -803,7 +837,8 @@ bool WebAppShortcutCreator::UpdatePlist(const base::FilePath& app_path) const {
app_mode::kShortcutBrowserBundleIDPlaceholder, nil]; app_mode::kShortcutBrowserBundleIDPlaceholder, nil];
NSString* plist_path = GetPlistPath(app_path); NSString* plist_path = GetPlistPath(app_path);
NSMutableDictionary* plist = ReadPlist(plist_path); NSMutableDictionary* plist =
[NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
NSArray* keys = [plist allKeys]; NSArray* keys = [plist allKeys];
// 1. Fill in variables. // 1. Fill in variables.
...@@ -873,8 +908,7 @@ bool WebAppShortcutCreator::UpdateDisplayName( ...@@ -873,8 +908,7 @@ bool WebAppShortcutCreator::UpdateDisplayName(
NSString* bundle_name = base::SysUTF16ToNSString(info_->title); NSString* bundle_name = base::SysUTF16ToNSString(info_->title);
NSString* display_name = base::SysUTF16ToNSString(info_->title); NSString* display_name = base::SysUTF16ToNSString(info_->title);
if (HasExistingExtensionShimForDifferentProfile( if (HasExistingExtensionShimForDifferentProfile(
GetApplicationsDirname(), info_->extension_id, GetApplicationsDirname(), info_->extension_id, info_->profile_path)) {
info_->profile_path.BaseName())) {
display_name = [bundle_name display_name = [bundle_name
stringByAppendingString:base::SysUTF8ToNSString( stringByAppendingString:base::SysUTF8ToNSString(
" (" + info_->profile_name + ")")]; " (" + info_->profile_name + ")")];
...@@ -912,8 +946,9 @@ bool WebAppShortcutCreator::UpdateIcon(const base::FilePath& app_path) const { ...@@ -912,8 +946,9 @@ bool WebAppShortcutCreator::UpdateIcon(const base::FilePath& app_path) const {
std::vector<base::FilePath> WebAppShortcutCreator::GetAppBundlesByIdUnsorted() std::vector<base::FilePath> WebAppShortcutCreator::GetAppBundlesByIdUnsorted()
const { const {
const std::string bundle_id = GetBundleIdentifier();
base::ScopedCFTypeRef<CFStringRef> bundle_id_cf( base::ScopedCFTypeRef<CFStringRef> bundle_id_cf(
base::SysUTF8ToCFStringRef(GetBundleIdentifier())); base::SysUTF8ToCFStringRef(bundle_id));
// Retrieve the URLs found by LaunchServices. // Retrieve the URLs found by LaunchServices.
base::scoped_nsobject<NSArray> urls(base::mac::CFToNSCast( base::scoped_nsobject<NSArray> urls(base::mac::CFToNSCast(
...@@ -924,9 +959,22 @@ std::vector<base::FilePath> WebAppShortcutCreator::GetAppBundlesByIdUnsorted() ...@@ -924,9 +959,22 @@ std::vector<base::FilePath> WebAppShortcutCreator::GetAppBundlesByIdUnsorted()
for (NSURL* url : urls.get()) { for (NSURL* url : urls.get()) {
NSString* path_string = [url path]; NSString* path_string = [url path];
base::FilePath path([path_string fileSystemRepresentation]); base::FilePath path([path_string fileSystemRepresentation]);
if (HasSameUserDataDir(path)) if (BundleInfoPlist(path).IsForCurrentUserDataDir())
paths.push_back(path); paths.push_back(path);
} }
// LaunchServices can fail to locate a recently-created bundle. Search
// for an app in the applications folder to handle this case.
// https://crbug.com/937703
if (paths.empty()) {
std::list<BundleInfoPlist> bundles_info = BundleInfoPlist::GetAllInPath(
GetApplicationsDirname(), false /* recursive */);
for (const auto& info : bundles_info) {
if (info.IsForCurrentUserDataDir() && info.GetBundleId() == bundle_id)
paths.push_back(info.bundle_path());
}
}
return paths; return paths;
} }
...@@ -1063,15 +1111,18 @@ void UpdatePlatformShortcuts(const base::FilePath& app_data_path, ...@@ -1063,15 +1111,18 @@ void UpdatePlatformShortcuts(const base::FilePath& app_data_path,
void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) { void DeleteAllShortcutsForProfile(const base::FilePath& profile_path) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK); base::BlockingType::MAY_BLOCK);
const std::string profile_base_name = profile_path.BaseName().value(); std::list<BundleInfoPlist> bundles_info = BundleInfoPlist::GetAllInPath(
std::vector<base::FilePath> bundles = GetAllAppBundlesInPath( profile_path.Append(chrome::kWebAppDirname), true /* recursive */);
profile_path.Append(chrome::kWebAppDirname), profile_base_name); for (const auto& info : bundles_info) {
if (!info.IsForCurrentUserDataDir())
continue;
if (!info.IsForProfile(profile_path))
continue;
for (std::vector<base::FilePath>::const_iterator it = bundles.begin();
it != bundles.end(); ++it) {
std::unique_ptr<web_app::ShortcutInfo> shortcut_info = std::unique_ptr<web_app::ShortcutInfo> shortcut_info =
BuildShortcutInfoFromBundle(*it); BuildShortcutInfoFromBundle(info.bundle_path());
WebAppShortcutCreator shortcut_creator(it->DirName(), shortcut_info.get()); WebAppShortcutCreator shortcut_creator(info.bundle_path().DirName(),
shortcut_info.get());
shortcut_creator.DeleteShortcuts(); shortcut_creator.DeleteShortcuts();
} }
} }
......
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