blob: 7e5f3fa0b7e13065d4c28b9152522ee69f790d25 [file] [log] [blame]
// 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.
#import "chrome/common/mac/app_mode_chrome_locator.h"
#import <AppKit/AppKit.h>
#include <CoreFoundation/CoreFoundation.h>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/mac/foundation_util.h"
#include "base/optional.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/mac/app_mode_common.h"
namespace app_mode {
namespace {
struct PathAndStructure {
NSString* framework_dylib_path; // weak
bool is_new_app_structure;
base::Optional<PathAndStructure> GetFrameworkDylibPathAndStructure(
NSString* bundle_path,
NSString* version) {
// Framework.framework/
// Versions/<version>/Chromium Framework
NSString* path = [NSString pathWithComponents:@[
bundle_path, @"Contents", @"Frameworks", @(chrome::kFrameworkName),
@"Versions", version, @(chrome::kFrameworkExecutableName)
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
return PathAndStructure{path, true};
//<version>/Chromium Framework.framework/
// Versions/A/Chromium Framework
path = [NSString pathWithComponents:@[
bundle_path, @"Contents", @"Versions", version, @(chrome::kFrameworkName),
@"Versions", @"A", @(chrome::kFrameworkExecutableName)
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
return PathAndStructure{path, false};
return base::nullopt;
} // namespace
bool FindBundleById(NSString* bundle_id, base::FilePath* out_bundle) {
NSWorkspace* ws = [NSWorkspace sharedWorkspace];
NSString *bundlePath = [ws absolutePathForAppBundleWithIdentifier:bundle_id];
if (!bundlePath)
return false;
*out_bundle = base::mac::NSStringToFilePath(bundlePath);
return true;
bool GetChromeBundleInfo(const base::FilePath& chrome_bundle,
const std::string& version_str,
base::FilePath* executable_path,
base::FilePath* framework_path,
base::FilePath* framework_dylib_path) {
NSString* cr_bundle_path = base::mac::FilePathToNSString(chrome_bundle);
NSBundle* cr_bundle = [NSBundle bundleWithPath:cr_bundle_path];
if (!cr_bundle)
return false;
// Try to get the version requested, if present.
base::Optional<PathAndStructure> framework_path_and_structure;
if (!version_str.empty()) {
framework_path_and_structure = GetFrameworkDylibPathAndStructure(
cr_bundle_path, base::SysUTF8ToNSString(version_str));
// If the version requested is not present, or no specific version was
// requested, fall back to the "current" version. For new-style bundle
// structures, use the "Current" symlink. (This will intentionally return nil
// with the old bundle structure.)
// Note that the scenario where a specific version was requested but is not
// present is a "should not happen" scenario. Chromium, while it is running,
// maintains a link to the currently running version, and this function's
// caller checked to see if the Chromium was still running. However, even in
// this bizarre case, it's best to find _some_ Chromium.
if (!framework_path_and_structure) {
framework_path_and_structure =
GetFrameworkDylibPathAndStructure(cr_bundle_path, @"Current");
if (framework_path_and_structure) {
framework_path_and_structure->framework_dylib_path =
->framework_dylib_path stringByResolvingSymlinksInPath];
// At this point it is known that it is an old-style bundle structure (or a
// rather broken new-style bundle). Try explicitly specifying the version of
// the framework matching the outer bundle version.
if (!framework_path_and_structure) {
NSString* cr_version = base::mac::ObjCCast<NSString>([cr_bundle
if (cr_version) {
framework_path_and_structure =
GetFrameworkDylibPathAndStructure(cr_bundle_path, cr_version);
if (!framework_path_and_structure)
return false;
// A few sanity checks.
BOOL is_directory;
BOOL exists = [[NSFileManager defaultManager]
if (!exists || is_directory)
return false;
NSString* cr_framework_path;
if (framework_path_and_structure->is_new_app_structure) {
// For the path to the framework version itself, remove the framework name.
cr_framework_path =
->framework_dylib_path stringByDeletingLastPathComponent];
} else {
// For the path to the framework itself, remove the framework name ...
cr_framework_path =
->framework_dylib_path stringByDeletingLastPathComponent];
// ... the "A" ...
cr_framework_path = [cr_framework_path stringByDeletingLastPathComponent];
// ... and the "Versions" directory.
cr_framework_path = [cr_framework_path stringByDeletingLastPathComponent];
// Everything is OK; copy the output parameters.
*executable_path = base::mac::NSStringToFilePath([cr_bundle executablePath]);
*framework_path = base::mac::NSStringToFilePath(cr_framework_path);
*framework_dylib_path = base::mac::NSStringToFilePath(
return true;
} // namespace app_mode