1*6777b538SAndroid Build Coastguard Worker// Copyright 2013 The Chromium Authors 2*6777b538SAndroid Build Coastguard Worker// Use of this source code is governed by a BSD-style license that can be 3*6777b538SAndroid Build Coastguard Worker// found in the LICENSE file. 4*6777b538SAndroid Build Coastguard Worker 5*6777b538SAndroid Build Coastguard Worker#import "base/mac/launch_application.h" 6*6777b538SAndroid Build Coastguard Worker 7*6777b538SAndroid Build Coastguard Worker#include "base/apple/bridging.h" 8*6777b538SAndroid Build Coastguard Worker#include "base/apple/foundation_util.h" 9*6777b538SAndroid Build Coastguard Worker#include "base/command_line.h" 10*6777b538SAndroid Build Coastguard Worker#include "base/functional/callback.h" 11*6777b538SAndroid Build Coastguard Worker#include "base/logging.h" 12*6777b538SAndroid Build Coastguard Worker#include "base/mac/launch_services_spi.h" 13*6777b538SAndroid Build Coastguard Worker#include "base/mac/mac_util.h" 14*6777b538SAndroid Build Coastguard Worker#include "base/metrics/histogram_functions.h" 15*6777b538SAndroid Build Coastguard Worker#include "base/strings/sys_string_conversions.h" 16*6777b538SAndroid Build Coastguard Worker#include "base/types/expected.h" 17*6777b538SAndroid Build Coastguard Worker 18*6777b538SAndroid Build Coastguard Workernamespace base::mac { 19*6777b538SAndroid Build Coastguard Worker 20*6777b538SAndroid Build Coastguard Workernamespace { 21*6777b538SAndroid Build Coastguard Worker 22*6777b538SAndroid Build Coastguard Worker// These values are persisted to logs. Entries should not be renumbered and 23*6777b538SAndroid Build Coastguard Worker// numeric values should never be reused. 24*6777b538SAndroid Build Coastguard Workerenum class LaunchResult { 25*6777b538SAndroid Build Coastguard Worker kSuccess = 0, 26*6777b538SAndroid Build Coastguard Worker kSuccessDespiteError = 1, 27*6777b538SAndroid Build Coastguard Worker kFailure = 2, 28*6777b538SAndroid Build Coastguard Worker kMaxValue = kFailure, 29*6777b538SAndroid Build Coastguard Worker}; 30*6777b538SAndroid Build Coastguard Worker 31*6777b538SAndroid Build Coastguard Workervoid LogLaunchResult(LaunchResult result) { 32*6777b538SAndroid Build Coastguard Worker UmaHistogramEnumeration("Mac.LaunchApplicationResult", result); 33*6777b538SAndroid Build Coastguard Worker} 34*6777b538SAndroid Build Coastguard Worker 35*6777b538SAndroid Build Coastguard WorkerNSArray* CommandLineArgsToArgsArray(const CommandLineArgs& command_line_args) { 36*6777b538SAndroid Build Coastguard Worker if (const CommandLine* command_line = 37*6777b538SAndroid Build Coastguard Worker absl::get_if<CommandLine>(&command_line_args)) { 38*6777b538SAndroid Build Coastguard Worker const auto& argv = command_line->argv(); 39*6777b538SAndroid Build Coastguard Worker size_t argc = argv.size(); 40*6777b538SAndroid Build Coastguard Worker DCHECK_GT(argc, 0lu); 41*6777b538SAndroid Build Coastguard Worker 42*6777b538SAndroid Build Coastguard Worker NSMutableArray* args_array = [NSMutableArray arrayWithCapacity:argc - 1]; 43*6777b538SAndroid Build Coastguard Worker // NSWorkspace automatically adds the binary path as the first argument and 44*6777b538SAndroid Build Coastguard Worker // thus it should not be included in the list. 45*6777b538SAndroid Build Coastguard Worker for (size_t i = 1; i < argc; ++i) { 46*6777b538SAndroid Build Coastguard Worker [args_array addObject:base::SysUTF8ToNSString(argv[i])]; 47*6777b538SAndroid Build Coastguard Worker } 48*6777b538SAndroid Build Coastguard Worker 49*6777b538SAndroid Build Coastguard Worker return args_array; 50*6777b538SAndroid Build Coastguard Worker } 51*6777b538SAndroid Build Coastguard Worker 52*6777b538SAndroid Build Coastguard Worker if (const std::vector<std::string>* string_vector = 53*6777b538SAndroid Build Coastguard Worker absl::get_if<std::vector<std::string>>(&command_line_args)) { 54*6777b538SAndroid Build Coastguard Worker NSMutableArray* args_array = 55*6777b538SAndroid Build Coastguard Worker [NSMutableArray arrayWithCapacity:string_vector->size()]; 56*6777b538SAndroid Build Coastguard Worker for (const auto& arg : *string_vector) { 57*6777b538SAndroid Build Coastguard Worker [args_array addObject:base::SysUTF8ToNSString(arg)]; 58*6777b538SAndroid Build Coastguard Worker } 59*6777b538SAndroid Build Coastguard Worker 60*6777b538SAndroid Build Coastguard Worker return args_array; 61*6777b538SAndroid Build Coastguard Worker } 62*6777b538SAndroid Build Coastguard Worker 63*6777b538SAndroid Build Coastguard Worker return @[]; 64*6777b538SAndroid Build Coastguard Worker} 65*6777b538SAndroid Build Coastguard Worker 66*6777b538SAndroid Build Coastguard WorkerNSWorkspaceOpenConfiguration* GetOpenConfiguration( 67*6777b538SAndroid Build Coastguard Worker LaunchApplicationOptions options, 68*6777b538SAndroid Build Coastguard Worker const CommandLineArgs& command_line_args) { 69*6777b538SAndroid Build Coastguard Worker NSWorkspaceOpenConfiguration* config = 70*6777b538SAndroid Build Coastguard Worker [NSWorkspaceOpenConfiguration configuration]; 71*6777b538SAndroid Build Coastguard Worker 72*6777b538SAndroid Build Coastguard Worker config.arguments = CommandLineArgsToArgsArray(command_line_args); 73*6777b538SAndroid Build Coastguard Worker 74*6777b538SAndroid Build Coastguard Worker config.activates = options.activate; 75*6777b538SAndroid Build Coastguard Worker config.createsNewApplicationInstance = options.create_new_instance; 76*6777b538SAndroid Build Coastguard Worker config.promptsUserIfNeeded = options.prompt_user_if_needed; 77*6777b538SAndroid Build Coastguard Worker 78*6777b538SAndroid Build Coastguard Worker if (options.hidden_in_background) { 79*6777b538SAndroid Build Coastguard Worker config.addsToRecentItems = NO; 80*6777b538SAndroid Build Coastguard Worker config.hides = YES; 81*6777b538SAndroid Build Coastguard Worker config._additionalLSOpenOptions = @{ 82*6777b538SAndroid Build Coastguard Worker apple::CFToNSPtrCast(_kLSOpenOptionBackgroundLaunchKey) : @YES, 83*6777b538SAndroid Build Coastguard Worker }; 84*6777b538SAndroid Build Coastguard Worker } 85*6777b538SAndroid Build Coastguard Worker 86*6777b538SAndroid Build Coastguard Worker return config; 87*6777b538SAndroid Build Coastguard Worker} 88*6777b538SAndroid Build Coastguard Worker 89*6777b538SAndroid Build Coastguard Worker// Sometimes macOS 11 and 12 report an error launching even though the launch 90*6777b538SAndroid Build Coastguard Worker// succeeded anyway. This helper returns true for the error codes we have 91*6777b538SAndroid Build Coastguard Worker// observed where scanning the list of running applications appears to be a 92*6777b538SAndroid Build Coastguard Worker// usable workaround for this. 93*6777b538SAndroid Build Coastguard Workerbool ShouldScanRunningAppsForError(NSError* error) { 94*6777b538SAndroid Build Coastguard Worker if (!error) { 95*6777b538SAndroid Build Coastguard Worker return false; 96*6777b538SAndroid Build Coastguard Worker } 97*6777b538SAndroid Build Coastguard Worker if (error.domain == NSCocoaErrorDomain && 98*6777b538SAndroid Build Coastguard Worker error.code == NSFileReadUnknownError) { 99*6777b538SAndroid Build Coastguard Worker return true; 100*6777b538SAndroid Build Coastguard Worker } 101*6777b538SAndroid Build Coastguard Worker if (error.domain == NSOSStatusErrorDomain && error.code == procNotFound) { 102*6777b538SAndroid Build Coastguard Worker return true; 103*6777b538SAndroid Build Coastguard Worker } 104*6777b538SAndroid Build Coastguard Worker return false; 105*6777b538SAndroid Build Coastguard Worker} 106*6777b538SAndroid Build Coastguard Worker 107*6777b538SAndroid Build Coastguard Workervoid LogResultAndInvokeCallback(const base::FilePath& app_bundle_path, 108*6777b538SAndroid Build Coastguard Worker bool create_new_instance, 109*6777b538SAndroid Build Coastguard Worker LaunchApplicationCallback callback, 110*6777b538SAndroid Build Coastguard Worker NSRunningApplication* app, 111*6777b538SAndroid Build Coastguard Worker NSError* error) { 112*6777b538SAndroid Build Coastguard Worker // Sometimes macOS 11 and 12 report an error launching even though the 113*6777b538SAndroid Build Coastguard Worker // launch succeeded anyway. To work around such cases, check if we can 114*6777b538SAndroid Build Coastguard Worker // find a running application matching the app we were trying to launch. 115*6777b538SAndroid Build Coastguard Worker // Only do this if `options.create_new_instance` is false though, as 116*6777b538SAndroid Build Coastguard Worker // otherwise we wouldn't know which instance to return. 117*6777b538SAndroid Build Coastguard Worker if ((MacOSMajorVersion() == 11 || MacOSMajorVersion() == 12) && 118*6777b538SAndroid Build Coastguard Worker !create_new_instance && !app && ShouldScanRunningAppsForError(error)) { 119*6777b538SAndroid Build Coastguard Worker NSArray<NSRunningApplication*>* all_apps = 120*6777b538SAndroid Build Coastguard Worker NSWorkspace.sharedWorkspace.runningApplications; 121*6777b538SAndroid Build Coastguard Worker for (NSRunningApplication* running_app in all_apps) { 122*6777b538SAndroid Build Coastguard Worker if (apple::NSURLToFilePath(running_app.bundleURL) == app_bundle_path) { 123*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << "Launch succeeded despite error: " 124*6777b538SAndroid Build Coastguard Worker << base::SysNSStringToUTF8(error.localizedDescription); 125*6777b538SAndroid Build Coastguard Worker app = running_app; 126*6777b538SAndroid Build Coastguard Worker break; 127*6777b538SAndroid Build Coastguard Worker } 128*6777b538SAndroid Build Coastguard Worker } 129*6777b538SAndroid Build Coastguard Worker if (app) { 130*6777b538SAndroid Build Coastguard Worker error = nil; 131*6777b538SAndroid Build Coastguard Worker } 132*6777b538SAndroid Build Coastguard Worker LogLaunchResult(app ? LaunchResult::kSuccessDespiteError 133*6777b538SAndroid Build Coastguard Worker : LaunchResult::kFailure); 134*6777b538SAndroid Build Coastguard Worker } else { 135*6777b538SAndroid Build Coastguard Worker LogLaunchResult(app ? LaunchResult::kSuccess : LaunchResult::kFailure); 136*6777b538SAndroid Build Coastguard Worker } 137*6777b538SAndroid Build Coastguard Worker 138*6777b538SAndroid Build Coastguard Worker if (error) { 139*6777b538SAndroid Build Coastguard Worker LOG(ERROR) << base::SysNSStringToUTF8(error.localizedDescription); 140*6777b538SAndroid Build Coastguard Worker std::move(callback).Run(nil, error); 141*6777b538SAndroid Build Coastguard Worker } else { 142*6777b538SAndroid Build Coastguard Worker std::move(callback).Run(app, nil); 143*6777b538SAndroid Build Coastguard Worker } 144*6777b538SAndroid Build Coastguard Worker} 145*6777b538SAndroid Build Coastguard Worker 146*6777b538SAndroid Build Coastguard Worker} // namespace 147*6777b538SAndroid Build Coastguard Worker 148*6777b538SAndroid Build Coastguard Workervoid LaunchApplication(const base::FilePath& app_bundle_path, 149*6777b538SAndroid Build Coastguard Worker const CommandLineArgs& command_line_args, 150*6777b538SAndroid Build Coastguard Worker const std::vector<std::string>& url_specs, 151*6777b538SAndroid Build Coastguard Worker LaunchApplicationOptions options, 152*6777b538SAndroid Build Coastguard Worker LaunchApplicationCallback callback) { 153*6777b538SAndroid Build Coastguard Worker __block LaunchApplicationCallback callback_block_access = 154*6777b538SAndroid Build Coastguard Worker base::BindOnce(&LogResultAndInvokeCallback, app_bundle_path, 155*6777b538SAndroid Build Coastguard Worker options.create_new_instance, std::move(callback)); 156*6777b538SAndroid Build Coastguard Worker 157*6777b538SAndroid Build Coastguard Worker NSURL* bundle_url = apple::FilePathToNSURL(app_bundle_path); 158*6777b538SAndroid Build Coastguard Worker if (!bundle_url) { 159*6777b538SAndroid Build Coastguard Worker dispatch_async(dispatch_get_main_queue(), ^{ 160*6777b538SAndroid Build Coastguard Worker std::move(callback_block_access) 161*6777b538SAndroid Build Coastguard Worker .Run(nil, [NSError errorWithDomain:NSCocoaErrorDomain 162*6777b538SAndroid Build Coastguard Worker code:NSFileNoSuchFileError 163*6777b538SAndroid Build Coastguard Worker userInfo:nil]); 164*6777b538SAndroid Build Coastguard Worker }); 165*6777b538SAndroid Build Coastguard Worker return; 166*6777b538SAndroid Build Coastguard Worker } 167*6777b538SAndroid Build Coastguard Worker 168*6777b538SAndroid Build Coastguard Worker NSMutableArray* ns_urls = nil; 169*6777b538SAndroid Build Coastguard Worker if (!url_specs.empty()) { 170*6777b538SAndroid Build Coastguard Worker ns_urls = [NSMutableArray arrayWithCapacity:url_specs.size()]; 171*6777b538SAndroid Build Coastguard Worker for (const auto& url_spec : url_specs) { 172*6777b538SAndroid Build Coastguard Worker [ns_urls 173*6777b538SAndroid Build Coastguard Worker addObject:[NSURL URLWithString:base::SysUTF8ToNSString(url_spec)]]; 174*6777b538SAndroid Build Coastguard Worker } 175*6777b538SAndroid Build Coastguard Worker } 176*6777b538SAndroid Build Coastguard Worker 177*6777b538SAndroid Build Coastguard Worker void (^action_block)(NSRunningApplication*, NSError*) = 178*6777b538SAndroid Build Coastguard Worker ^void(NSRunningApplication* app, NSError* error) { 179*6777b538SAndroid Build Coastguard Worker dispatch_async(dispatch_get_main_queue(), ^{ 180*6777b538SAndroid Build Coastguard Worker std::move(callback_block_access).Run(app, error); 181*6777b538SAndroid Build Coastguard Worker }); 182*6777b538SAndroid Build Coastguard Worker }; 183*6777b538SAndroid Build Coastguard Worker 184*6777b538SAndroid Build Coastguard Worker NSWorkspaceOpenConfiguration* configuration = 185*6777b538SAndroid Build Coastguard Worker GetOpenConfiguration(options, command_line_args); 186*6777b538SAndroid Build Coastguard Worker 187*6777b538SAndroid Build Coastguard Worker if (ns_urls) { 188*6777b538SAndroid Build Coastguard Worker [NSWorkspace.sharedWorkspace openURLs:ns_urls 189*6777b538SAndroid Build Coastguard Worker withApplicationAtURL:bundle_url 190*6777b538SAndroid Build Coastguard Worker configuration:configuration 191*6777b538SAndroid Build Coastguard Worker completionHandler:action_block]; 192*6777b538SAndroid Build Coastguard Worker } else { 193*6777b538SAndroid Build Coastguard Worker [NSWorkspace.sharedWorkspace openApplicationAtURL:bundle_url 194*6777b538SAndroid Build Coastguard Worker configuration:configuration 195*6777b538SAndroid Build Coastguard Worker completionHandler:action_block]; 196*6777b538SAndroid Build Coastguard Worker } 197*6777b538SAndroid Build Coastguard Worker} 198*6777b538SAndroid Build Coastguard Worker 199*6777b538SAndroid Build Coastguard Worker} // namespace base::mac 200