| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 // On Mac, shortcuts can't have command-line arguments. Instead, produce small | |
| 6 // app bundles which locate the Chromium framework and load it, passing the | |
| 7 // appropriate data. This is the code for such an app bundle. It should be kept | |
| 8 // minimal and do as little work as possible (with as much work done on | |
| 9 // framework side as possible). | |
| 10 | |
| 11 #include <dlfcn.h> | |
| 12 | |
| 13 #import <Cocoa/Cocoa.h> | |
| 14 | |
| 15 #include "base/command_line.h" | |
| 16 #include "base/files/file_path.h" | |
| 17 #include "base/files/file_util.h" | |
| 18 #include "base/logging.h" | |
| 19 #include "base/mac/foundation_util.h" | |
| 20 #include "base/mac/launch_services_util.h" | |
| 21 #include "base/mac/scoped_nsautorelease_pool.h" | |
| 22 #include "base/process/launch.h" | |
| 23 #include "base/strings/string_number_conversions.h" | |
| 24 #include "base/strings/sys_string_conversions.h" | |
| 25 #include "chrome/common/chrome_constants.h" | |
| 26 #include "chrome/common/chrome_switches.h" | |
| 27 #import "chrome/common/mac/app_mode_chrome_locator.h" | |
| 28 #include "chrome/common/mac/app_mode_common.h" | |
| 29 | |
| 30 namespace { | |
| 31 | |
| 32 typedef int (*StartFun)(const app_mode::ChromeAppModeInfo*); | |
| 33 | |
| 34 int LoadFrameworkAndStart(app_mode::ChromeAppModeInfo* info) { | |
| 35 using base::SysNSStringToUTF8; | |
| 36 using base::SysNSStringToUTF16; | |
| 37 using base::mac::CFToNSCast; | |
| 38 using base::mac::CFCastStrict; | |
| 39 using base::mac::NSToCFCast; | |
| 40 | |
| 41 base::mac::ScopedNSAutoreleasePool scoped_pool; | |
| 42 | |
| 43 // Get the current main bundle, i.e., that of the app loader that's running. | |
| 44 NSBundle* app_bundle = [NSBundle mainBundle]; | |
| 45 CHECK(app_bundle) << "couldn't get loader bundle"; | |
| 46 | |
| 47 // ** 1: Get path to outer Chrome bundle. | |
| 48 // Get the bundle ID of the browser that created this app bundle. | |
| 49 NSString* cr_bundle_id = base::mac::ObjCCast<NSString>( | |
| 50 [app_bundle objectForInfoDictionaryKey:app_mode::kBrowserBundleIDKey]); | |
| 51 CHECK(cr_bundle_id) << "couldn't get browser bundle ID"; | |
| 52 | |
| 53 // First check if Chrome exists at the last known location. | |
| 54 base::FilePath cr_bundle_path; | |
| 55 NSString* cr_bundle_path_ns = | |
| 56 [CFToNSCast(CFCastStrict<CFStringRef>(CFPreferencesCopyAppValue( | |
| 57 NSToCFCast(app_mode::kLastRunAppBundlePathPrefsKey), | |
| 58 NSToCFCast(cr_bundle_id)))) autorelease]; | |
| 59 cr_bundle_path = base::mac::NSStringToFilePath(cr_bundle_path_ns); | |
| 60 bool found_bundle = | |
| 61 !cr_bundle_path.empty() && base::DirectoryExists(cr_bundle_path); | |
| 62 | |
| 63 if (!found_bundle) { | |
| 64 // If no such bundle path exists, try to search by bundle ID. | |
| 65 if (!app_mode::FindBundleById(cr_bundle_id, &cr_bundle_path)) { | |
| 66 // TODO(jeremy): Display UI to allow user to manually locate the Chrome | |
| 67 // bundle. | |
| 68 LOG(FATAL) << "Failed to locate bundle by identifier"; | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 // ** 2: Read the running Chrome version. | |
| 73 // The user_data_dir for shims actually contains the app_data_path. | |
| 74 // I.e. <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/ | |
| 75 base::FilePath app_data_dir = base::mac::NSStringToFilePath([app_bundle | |
| 76 objectForInfoDictionaryKey:app_mode::kCrAppModeUserDataDirKey]); | |
| 77 base::FilePath user_data_dir = app_data_dir.DirName().DirName().DirName(); | |
| 78 CHECK(!user_data_dir.empty()); | |
| 79 | |
| 80 // If the version file does not exist, |cr_version_str| will be empty and | |
| 81 // app_mode::GetChromeBundleInfo will default to the latest version. | |
| 82 base::FilePath cr_version_str; | |
| 83 base::ReadSymbolicLink( | |
| 84 user_data_dir.Append(app_mode::kRunningChromeVersionSymlinkName), | |
| 85 &cr_version_str); | |
| 86 | |
| 87 // If the version file does exist, it may have been left by a crashed Chrome | |
| 88 // process. Ensure the process is still running. | |
| 89 if (!cr_version_str.empty()) { | |
| 90 NSArray* existing_chrome = [NSRunningApplication | |
| 91 runningApplicationsWithBundleIdentifier:cr_bundle_id]; | |
| 92 if ([existing_chrome count] == 0) | |
| 93 cr_version_str.clear(); | |
| 94 } | |
| 95 | |
| 96 // ** 3: Read information from the Chrome bundle. | |
| 97 base::FilePath executable_path; | |
| 98 base::FilePath version_path; | |
| 99 base::FilePath framework_shlib_path; | |
| 100 if (!app_mode::GetChromeBundleInfo(cr_bundle_path, | |
| 101 cr_version_str.value(), | |
| 102 &executable_path, | |
| 103 &version_path, | |
| 104 &framework_shlib_path)) { | |
| 105 LOG(FATAL) << "Couldn't ready Chrome bundle info"; | |
| 106 } | |
| 107 base::FilePath app_mode_bundle_path = | |
| 108 base::mac::NSStringToFilePath([app_bundle bundlePath]); | |
| 109 | |
| 110 // ** 4: Fill in ChromeAppModeInfo. | |
| 111 info->chrome_outer_bundle_path = cr_bundle_path; | |
| 112 info->chrome_versioned_path = version_path; | |
| 113 info->app_mode_bundle_path = app_mode_bundle_path; | |
| 114 | |
| 115 // Read information about the this app shortcut from the Info.plist. | |
| 116 // Don't check for null-ness on optional items. | |
| 117 NSDictionary* info_plist = [app_bundle infoDictionary]; | |
| 118 CHECK(info_plist) << "couldn't get loader Info.plist"; | |
| 119 | |
| 120 info->app_mode_id = SysNSStringToUTF8( | |
| 121 [info_plist objectForKey:app_mode::kCrAppModeShortcutIDKey]); | |
| 122 CHECK(info->app_mode_id.size()) << "couldn't get app shortcut ID"; | |
| 123 | |
| 124 info->app_mode_name = SysNSStringToUTF16( | |
| 125 [info_plist objectForKey:app_mode::kCrAppModeShortcutNameKey]); | |
| 126 | |
| 127 info->app_mode_url = SysNSStringToUTF8( | |
| 128 [info_plist objectForKey:app_mode::kCrAppModeShortcutURLKey]); | |
| 129 | |
| 130 info->user_data_dir = base::mac::NSStringToFilePath( | |
| 131 [info_plist objectForKey:app_mode::kCrAppModeUserDataDirKey]); | |
| 132 | |
| 133 info->profile_dir = base::mac::NSStringToFilePath( | |
| 134 [info_plist objectForKey:app_mode::kCrAppModeProfileDirKey]); | |
| 135 | |
| 136 // ** 5: Open the framework. | |
| 137 StartFun ChromeAppModeStart = NULL; | |
| 138 void* cr_dylib = dlopen(framework_shlib_path.value().c_str(), RTLD_LAZY); | |
| 139 if (cr_dylib) { | |
| 140 // Find the entry point. | |
| 141 ChromeAppModeStart = (StartFun)dlsym(cr_dylib, "ChromeAppModeStart"); | |
| 142 if (!ChromeAppModeStart) | |
| 143 LOG(ERROR) << "Couldn't get entry point: " << dlerror(); | |
| 144 } else { | |
| 145 LOG(ERROR) << "Couldn't load framework: " << dlerror(); | |
| 146 } | |
| 147 | |
| 148 if (ChromeAppModeStart) | |
| 149 return ChromeAppModeStart(info); | |
| 150 | |
| 151 LOG(ERROR) << "Loading Chrome failed, launching Chrome with command line"; | |
| 152 CommandLine command_line(executable_path); | |
| 153 // The user_data_dir from the plist is actually the app data dir. | |
| 154 command_line.AppendSwitchPath( | |
| 155 switches::kUserDataDir, | |
| 156 info->user_data_dir.DirName().DirName().DirName()); | |
| 157 if (CommandLine::ForCurrentProcess()->HasSwitch( | |
| 158 app_mode::kLaunchedByChromeProcessId) || | |
| 159 info->app_mode_id == app_mode::kAppListModeId) { | |
| 160 // Pass --app-shim-error to have Chrome rebuild this shim. | |
| 161 // If Chrome has rebuilt this shim once already, then rebuilding doesn't fix | |
| 162 // the problem, so don't try again. | |
| 163 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
| 164 app_mode::kLaunchedAfterRebuild)) { | |
| 165 command_line.AppendSwitchPath(app_mode::kAppShimError, | |
| 166 app_mode_bundle_path); | |
| 167 } | |
| 168 } else { | |
| 169 // If the shim was launched directly (instead of by Chrome), first ask | |
| 170 // Chrome to launch the app. Chrome will launch the shim again, the same | |
| 171 // error will occur and be handled above. This approach allows the app to be | |
| 172 // started without blocking on fixing the shim and guarantees that the | |
| 173 // profile is loaded when Chrome receives --app-shim-error. | |
| 174 command_line.AppendSwitchPath(switches::kProfileDirectory, | |
| 175 info->profile_dir); | |
| 176 command_line.AppendSwitchASCII(switches::kAppId, info->app_mode_id); | |
| 177 } | |
| 178 // Launch the executable directly since base::mac::OpenApplicationWithPath | |
| 179 // uses LSOpenApplication which doesn't pass command line arguments if the | |
| 180 // application is already running. | |
| 181 if (!base::LaunchProcess(command_line, base::LaunchOptions(), NULL)) { | |
| 182 LOG(ERROR) << "Could not launch Chrome: " | |
| 183 << command_line.GetCommandLineString(); | |
| 184 return 1; | |
| 185 } | |
| 186 | |
| 187 return 0; | |
| 188 } | |
| 189 | |
| 190 } // namespace | |
| 191 | |
| 192 __attribute__((visibility("default"))) | |
| 193 int main(int argc, char** argv) { | |
| 194 CommandLine::Init(argc, argv); | |
| 195 app_mode::ChromeAppModeInfo info; | |
| 196 | |
| 197 // Hard coded info parameters. | |
| 198 info.major_version = app_mode::kCurrentChromeAppModeInfoMajorVersion; | |
| 199 info.minor_version = app_mode::kCurrentChromeAppModeInfoMinorVersion; | |
| 200 info.argc = argc; | |
| 201 info.argv = argv; | |
| 202 | |
| 203 // Exit instead of returning to avoid the the removal of |main()| from stack | |
| 204 // backtraces under tail call optimization. | |
| 205 exit(LoadFrameworkAndStart(&info)); | |
| 206 } | |
| OLD | NEW |