File: chrome_main_app_mode_mac.mm

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (291 lines) | stat: -rw-r--r-- 12,422 bytes parent folder | download | duplicates (6)
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;
  }
}