1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
|
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// On Mac, one can't make shortcuts with command-line arguments. Instead, we
// produce small app bundles which locate the Chromium framework and load it,
// passing the appropriate data. This is the entry point into the framework for
// those app bundles.
#import <Cocoa/Cocoa.h>
#include <utility>
#include <vector>
#include "base/allocator/early_zone_registration_apple.h"
#include "base/apple/bundle_locations.h"
#include "base/apple/foundation_util.h"
#include "base/apple/osstatus_logging.h"
#include "base/at_exit.h"
#include "base/base_switches.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_sending_event.h"
#include "base/message_loop/message_pump_apple.h"
#include "base/message_loop/message_pump_type.h"
#include "base/metrics/histogram_macros_local.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/threading/thread.h"
#include "chrome/app/chrome_crash_reporter_client.h"
#include "chrome/app_shim/app_shim_controller.h"
#include "chrome/app_shim/app_shim_delegate.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/mac/app_mode_common.h"
#include "chrome/common/mac/app_shim.mojom.h"
#include "components/crash/core/app/crashpad.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/core/embedder/features.h"
#include "mojo/core/embedder/scoped_ipc_support.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/gurl.h"
// The NSApplication for app shims is a vanilla NSApplication, but
// implements the CrAppProtocol and CrAppControlPrototocol protocols to skip
// creating an autorelease pool in nested event loops, for example when
// displaying a context menu.
@interface AppShimApplication
: NSApplication <CrAppProtocol, CrAppControlProtocol>
@end
@implementation AppShimApplication {
BOOL _handlingSendEvent;
}
- (BOOL)isHandlingSendEvent {
return _handlingSendEvent;
}
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
_handlingSendEvent = handlingSendEvent;
}
- (void)enableScreenReaderCompleteModeAfterDelay:(BOOL)enable {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector
(enableScreenReaderCompleteMode)
object:nil];
if (enable) {
const float kTwoSecondDelay = 2.0;
[self performSelector:@selector(enableScreenReaderCompleteMode)
withObject:nil
afterDelay:kTwoSecondDelay];
}
}
- (void)enableScreenReaderCompleteMode {
AppShimDelegate* delegate =
base::apple::ObjCCastStrict<AppShimDelegate>(NSApp.delegate);
[delegate enableAccessibilitySupport:
chrome::mojom::AppShimScreenReaderSupportMode::kComplete];
}
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
// This is an undocumented attribute that's set when VoiceOver is turned
// on/off. We track VoiceOver state changes using KVO, but monitor this
// attribute in case other ATs use it to request accessibility activation.
if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
// When there are ATs that want to access this PWA's accessibility, we
// need to notify the browser process to enable accessibility. When ATs no
// longer need access to this PWA's accessibility, we don't want it to
// affect the browser in case other PWA apps or the browser itself still
// need to use accessbility.
if ([value boolValue]) {
[self enableScreenReaderCompleteModeAfterDelay:YES];
}
}
return [super accessibilitySetValue:value forAttribute:attribute];
}
- (NSAccessibilityRole)accessibilityRole {
AppShimDelegate* delegate =
base::apple::ObjCCastStrict<AppShimDelegate>(NSApp.delegate);
[delegate enableAccessibilitySupport:
chrome::mojom::AppShimScreenReaderSupportMode::kPartial];
return [super accessibilityRole];
}
@end
extern "C" {
// |ChromeAppModeStart()| is the point of entry into the framework from the
// app mode loader. There are cases where the Chromium framework may have
// changed in a way that is incompatible with an older shim (e.g. change to
// libc++ library linking). The function name is versioned to provide a way
// to force shim upgrades if they are launched before an updated version of
// Chromium can upgrade them; the old shim will not be able to dyload the
// new ChromeAppModeStart, so it will fall back to the upgrade path. See
// https://crbug.com/561205.
__attribute__((visibility("default"))) int APP_SHIM_ENTRY_POINT_NAME(
const app_mode::ChromeAppModeInfo* info);
} // extern "C"
int APP_SHIM_ENTRY_POINT_NAME(const app_mode::ChromeAppModeInfo* info) {
// The static constructor in //base will have registered PartitionAlloc as
// the default zone. Allow the //base instance in the main library to
// register it as well. Otherwise we end up passing memory to free() which
// was allocated by an unknown zone. See crbug.com/1274236 for details.
partition_alloc::AllowDoublePartitionAllocZoneRegistration();
base::CommandLine::Init(info->argc, info->argv);
@autoreleasepool {
base::AtExitManager exit_manager;
chrome::RegisterPathProvider();
// Set bundle paths. This loads the bundles.
base::apple::SetOverrideOuterBundlePath(
base::FilePath(info->chrome_outer_bundle_path));
base::apple::SetOverrideFrameworkBundlePath(
base::FilePath(info->chrome_framework_path));
// Note that `info->user_data_dir` for shims contains the app data path,
// <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/.
const base::FilePath user_data_dir =
base::FilePath(info->user_data_dir).DirName().DirName().DirName();
// TODO(crbug.com/40807881): Specify `user_data_dir` to CrashPad.
ChromeCrashReporterClient::Create();
crash_reporter::InitializeCrashpad(true, "app_shim");
base::PathService::OverrideAndCreateIfNeeded(
chrome::DIR_USER_DATA, user_data_dir, /*is_absolute=*/false,
/*create=*/false);
// Initialize features and field trials, either from command line or from
// file in user data dir.
AppShimController::PreInitFeatureState(
*base::CommandLine::ForCurrentProcess());
// Calculate the preferred locale used by Chrome. We can't use
// l10n_util::OverrideLocaleWithCocoaLocale() because it calls
// [base::apple::OuterBundle() preferredLocalizations] which gets
// localizations from the bundle of the running app (i.e. it is equivalent
// to [[NSBundle mainBundle] preferredLocalizations]) instead of the
// target bundle.
NSArray<NSString*>* preferred_languages = NSLocale.preferredLanguages;
NSArray<NSString*>* supported_languages =
base::apple::OuterBundle().localizations;
std::string preferred_localization;
for (NSString* __strong language in preferred_languages) {
// We must convert the "-" separator to "_" to be compatible with
// NSBundle::localizations() e.g. "en-GB" becomes "en_GB".
// See https://crbug.com/913345.
language = [language stringByReplacingOccurrencesOfString:@"-"
withString:@"_"];
if ([supported_languages containsObject:language]) {
preferred_localization = base::SysNSStringToUTF8(language);
break;
}
// For Chinese and Serbian, the preferred and supported languages don't
// match due to script components and causes us to fall back to the next
// matched language. e.g. Simplified Chinese is presented as 'zh_CN' in
// supported_languages, but as 'zh_Hans_CN' in preferred_languages.
// Instead of falling back, adjust those 3 language codes to match
// language codes provided in supported_languages.
if ([language hasPrefix:@"zh_Hans"]) {
language = @"zh_CN";
} else if ([language hasPrefix:@"zh_Hant"]) {
language = @"zh_TW";
} else if ([language hasPrefix:@"sr_Latn"]) {
language = @"sr_Latn_RS";
} else {
// Check for language support without the region component.
language = [language componentsSeparatedByString:@"_"][0];
}
if ([supported_languages containsObject:language]) {
preferred_localization = base::SysNSStringToUTF8(language);
break;
}
// Avoid defaulting to English or another unintended language when no
// clear match is found. e.g. if there is no specific match for
// "sr_Latn_RS" in supported_languages, it can at least fall back to a
// generic Serbian language code ("sr").
language = [language componentsSeparatedByString:@"_"][0];
if ([supported_languages containsObject:language]) {
preferred_localization = base::SysNSStringToUTF8(language);
break;
}
}
std::string locale = l10n_util::NormalizeLocale(
l10n_util::GetApplicationLocale(preferred_localization));
// Load localized strings and mouse cursor images.
ui::ResourceBundle::InitSharedInstanceWithLocale(
locale, nullptr, ui::ResourceBundle::LOAD_COMMON_RESOURCES);
ChromeContentClient chrome_content_client;
content::SetContentClient(&chrome_content_client);
// Local histogram to let tests verify that histograms are emitted properly.
LOCAL_HISTOGRAM_BOOLEAN("AppShim.Launched", true);
// Launch the IO thread.
base::Thread::Options io_thread_options;
io_thread_options.message_pump_type = base::MessagePumpType::IO;
base::Thread* io_thread = new base::Thread("CrAppShimIO");
io_thread->StartWithOptions(std::move(io_thread_options));
// It's necessary to call Mojo's InitFeatures() to ensure we're using the
// same IPC implementation as the browser.
mojo::core::InitFeatures();
// Create a ThreadPool, but don't start it yet until we have fully
// initialized base::Feature and field trial support.
base::ThreadPoolInstance::Create("AppShim");
// We're using an isolated Mojo connection between the browser and this
// process, so this process must act as a broker.
mojo::core::Configuration config;
config.is_broker_process = true;
mojo::core::Init(config);
mojo::core::ScopedIPCSupport ipc_support(
io_thread->task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
// Initialize the NSApplication (and ensure that it was not previously
// initialized).
[AppShimApplication sharedApplication];
CHECK([NSApp isKindOfClass:[AppShimApplication class]]);
base::SingleThreadTaskExecutor main_task_executor(
base::MessagePumpType::UI);
ui::WindowResizeHelperMac::Get()->Init(main_task_executor.task_runner());
base::PlatformThread::SetName("CrAppShimMain");
AppShimController::Params controller_params;
controller_params.user_data_dir = user_data_dir;
// Similarly, extract the full profile path from |info->user_data_dir|.
// Ignore |info->profile_dir| because it is only the relative path (unless
// it is empty, in which case this is a profile-agnostic app).
if (!base::FilePath(info->profile_dir).empty()) {
controller_params.profile_dir =
base::FilePath(info->user_data_dir).DirName().DirName();
}
controller_params.app_id = info->app_mode_id;
controller_params.app_name = base::UTF8ToUTF16(info->app_mode_name);
controller_params.app_url = GURL(info->app_mode_url);
controller_params.io_thread_runner = io_thread->task_runner();
AppShimController controller(controller_params);
base::RunLoop().Run();
return 0;
}
}
|