browser_mac.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. // Copyright (c) 2013 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/browser.h"
  5. #include <memory>
  6. #include <string>
  7. #include <utility>
  8. #include "base/i18n/rtl.h"
  9. #include "base/mac/bundle_locations.h"
  10. #include "base/mac/foundation_util.h"
  11. #include "base/mac/mac_util.h"
  12. #include "base/mac/mac_util.mm"
  13. #include "base/mac/scoped_cftyperef.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/strings/sys_string_conversions.h"
  16. #include "chrome/browser/browser_process.h"
  17. #include "net/base/mac/url_conversions.h"
  18. #include "shell/browser/badging/badge_manager.h"
  19. #include "shell/browser/mac/dict_util.h"
  20. #include "shell/browser/mac/electron_application.h"
  21. #include "shell/browser/mac/electron_application_delegate.h"
  22. #include "shell/browser/native_window.h"
  23. #include "shell/browser/window_list.h"
  24. #include "shell/common/api/electron_api_native_image.h"
  25. #include "shell/common/application_info.h"
  26. #include "shell/common/gin_converters/image_converter.h"
  27. #include "shell/common/gin_helper/arguments.h"
  28. #include "shell/common/gin_helper/dictionary.h"
  29. #include "shell/common/gin_helper/error_thrower.h"
  30. #include "shell/common/gin_helper/promise.h"
  31. #include "shell/common/platform_util.h"
  32. #include "ui/gfx/image/image.h"
  33. #include "url/gurl.h"
  34. namespace electron {
  35. namespace {
  36. bool IsAppRTL() {
  37. const std::string& locale = g_browser_process->GetApplicationLocale();
  38. base::i18n::TextDirection text_direction =
  39. base::i18n::GetTextDirectionForLocaleInStartUp(locale.c_str());
  40. return text_direction == base::i18n::RIGHT_TO_LEFT;
  41. }
  42. NSString* GetAppPathForProtocol(const GURL& url) {
  43. NSURL* ns_url = [NSURL
  44. URLWithString:base::SysUTF8ToNSString(url.possibly_invalid_spec())];
  45. base::ScopedCFTypeRef<CFErrorRef> out_err;
  46. base::ScopedCFTypeRef<CFURLRef> openingApp(LSCopyDefaultApplicationURLForURL(
  47. (CFURLRef)ns_url, kLSRolesAll, out_err.InitializeInto()));
  48. if (out_err) {
  49. // likely kLSApplicationNotFoundErr
  50. return nullptr;
  51. }
  52. NSString* app_path = [base::mac::CFToNSCast(openingApp.get()) path];
  53. return app_path;
  54. }
  55. gfx::Image GetApplicationIconForProtocol(NSString* _Nonnull app_path) {
  56. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile:app_path];
  57. gfx::Image icon(image);
  58. return icon;
  59. }
  60. std::u16string GetAppDisplayNameForProtocol(NSString* app_path) {
  61. NSString* app_display_name =
  62. [[NSFileManager defaultManager] displayNameAtPath:app_path];
  63. return base::SysNSStringToUTF16(app_display_name);
  64. }
  65. #if !IS_MAS_BUILD()
  66. bool CheckLoginItemStatus(bool* is_hidden) {
  67. base::mac::LoginItemsFileList login_items;
  68. if (!login_items.Initialize())
  69. return false;
  70. base::ScopedCFTypeRef<LSSharedFileListItemRef> item(
  71. login_items.GetLoginItemForMainApp());
  72. if (!item.get())
  73. return false;
  74. if (is_hidden)
  75. *is_hidden = base::mac::IsHiddenLoginItem(item);
  76. return true;
  77. }
  78. #endif
  79. } // namespace
  80. v8::Local<v8::Promise> Browser::GetApplicationInfoForProtocol(
  81. v8::Isolate* isolate,
  82. const GURL& url) {
  83. gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
  84. v8::Local<v8::Promise> handle = promise.GetHandle();
  85. gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
  86. NSString* ns_app_path = GetAppPathForProtocol(url);
  87. if (!ns_app_path) {
  88. promise.RejectWithErrorMessage(
  89. "Unable to retrieve installation path to app");
  90. return handle;
  91. }
  92. std::u16string app_path = base::SysNSStringToUTF16(ns_app_path);
  93. std::u16string app_display_name = GetAppDisplayNameForProtocol(ns_app_path);
  94. gfx::Image app_icon = GetApplicationIconForProtocol(ns_app_path);
  95. dict.Set("name", app_display_name);
  96. dict.Set("path", app_path);
  97. dict.Set("icon", app_icon);
  98. promise.Resolve(dict);
  99. return handle;
  100. }
  101. void Browser::SetShutdownHandler(base::RepeatingCallback<bool()> handler) {
  102. [[AtomApplication sharedApplication] setShutdownHandler:std::move(handler)];
  103. }
  104. void Browser::Focus(gin::Arguments* args) {
  105. gin_helper::Dictionary opts;
  106. bool steal_focus = false;
  107. if (args->GetNext(&opts)) {
  108. gin_helper::ErrorThrower thrower(args->isolate());
  109. if (!opts.Get("steal", &steal_focus)) {
  110. thrower.ThrowError(
  111. "Expected options object to contain a 'steal' boolean property");
  112. return;
  113. }
  114. }
  115. [[AtomApplication sharedApplication] activateIgnoringOtherApps:steal_focus];
  116. }
  117. void Browser::Hide() {
  118. [[AtomApplication sharedApplication] hide:nil];
  119. }
  120. bool Browser::IsHidden() {
  121. return [[AtomApplication sharedApplication] isHidden];
  122. }
  123. void Browser::Show() {
  124. [[AtomApplication sharedApplication] unhide:nil];
  125. }
  126. void Browser::AddRecentDocument(const base::FilePath& path) {
  127. NSString* path_string = base::mac::FilePathToNSString(path);
  128. if (!path_string)
  129. return;
  130. NSURL* u = [NSURL fileURLWithPath:path_string];
  131. if (!u)
  132. return;
  133. [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:u];
  134. }
  135. void Browser::ClearRecentDocuments() {
  136. [[NSDocumentController sharedDocumentController] clearRecentDocuments:nil];
  137. }
  138. bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
  139. gin::Arguments* args) {
  140. NSString* identifier = [base::mac::MainBundle() bundleIdentifier];
  141. if (!identifier)
  142. return false;
  143. if (!Browser::IsDefaultProtocolClient(protocol, args))
  144. return false;
  145. NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()];
  146. CFStringRef protocol_cf = base::mac::NSToCFCast(protocol_ns);
  147. CFArrayRef bundleList = LSCopyAllHandlersForURLScheme(protocol_cf);
  148. if (!bundleList) {
  149. return false;
  150. }
  151. // On macOS, we can't query the default, but the handlers list seems to put
  152. // Apple's defaults first, so we'll use the first option that isn't our bundle
  153. CFStringRef other = nil;
  154. for (CFIndex i = 0; i < CFArrayGetCount(bundleList); ++i) {
  155. other =
  156. base::mac::CFCast<CFStringRef>(CFArrayGetValueAtIndex(bundleList, i));
  157. if (![identifier isEqualToString:(__bridge NSString*)other]) {
  158. break;
  159. }
  160. }
  161. // No other app was found set it to none instead of setting it back to itself.
  162. if ([identifier isEqualToString:(__bridge NSString*)other]) {
  163. other = base::mac::NSToCFCast(@"None");
  164. }
  165. OSStatus return_code = LSSetDefaultHandlerForURLScheme(protocol_cf, other);
  166. return return_code == noErr;
  167. }
  168. bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
  169. gin::Arguments* args) {
  170. if (protocol.empty())
  171. return false;
  172. NSString* identifier = [base::mac::MainBundle() bundleIdentifier];
  173. if (!identifier)
  174. return false;
  175. NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()];
  176. OSStatus return_code = LSSetDefaultHandlerForURLScheme(
  177. base::mac::NSToCFCast(protocol_ns), base::mac::NSToCFCast(identifier));
  178. return return_code == noErr;
  179. }
  180. bool Browser::IsDefaultProtocolClient(const std::string& protocol,
  181. gin::Arguments* args) {
  182. if (protocol.empty())
  183. return false;
  184. NSString* identifier = [base::mac::MainBundle() bundleIdentifier];
  185. if (!identifier)
  186. return false;
  187. NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()];
  188. base::ScopedCFTypeRef<CFStringRef> bundleId(
  189. LSCopyDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns)));
  190. if (!bundleId)
  191. return false;
  192. // Ensure the comparison is case-insensitive
  193. // as LS does not persist the case of the bundle id.
  194. NSComparisonResult result =
  195. [base::mac::CFToNSCast(bundleId) caseInsensitiveCompare:identifier];
  196. return result == NSOrderedSame;
  197. }
  198. std::u16string Browser::GetApplicationNameForProtocol(const GURL& url) {
  199. NSString* app_path = GetAppPathForProtocol(url);
  200. if (!app_path) {
  201. return std::u16string();
  202. }
  203. std::u16string app_display_name = GetAppDisplayNameForProtocol(app_path);
  204. return app_display_name;
  205. }
  206. bool Browser::SetBadgeCount(absl::optional<int> count) {
  207. DockSetBadgeText(!count.has_value() || count.value() != 0
  208. ? badging::BadgeManager::GetBadgeString(count)
  209. : "");
  210. if (count.has_value()) {
  211. badge_count_ = count.value();
  212. } else {
  213. badge_count_ = 0;
  214. }
  215. return true;
  216. }
  217. void Browser::SetUserActivity(const std::string& type,
  218. base::Value::Dict user_info,
  219. gin::Arguments* args) {
  220. std::string url_string;
  221. args->GetNext(&url_string);
  222. [[AtomApplication sharedApplication]
  223. setCurrentActivity:base::SysUTF8ToNSString(type)
  224. withUserInfo:DictionaryValueToNSDictionary(std::move(user_info))
  225. withWebpageURL:net::NSURLWithGURL(GURL(url_string))];
  226. }
  227. std::string Browser::GetCurrentActivityType() {
  228. NSUserActivity* userActivity =
  229. [[AtomApplication sharedApplication] getCurrentActivity];
  230. return base::SysNSStringToUTF8(userActivity.activityType);
  231. }
  232. void Browser::InvalidateCurrentActivity() {
  233. [[AtomApplication sharedApplication] invalidateCurrentActivity];
  234. }
  235. void Browser::ResignCurrentActivity() {
  236. [[AtomApplication sharedApplication] resignCurrentActivity];
  237. }
  238. void Browser::UpdateCurrentActivity(const std::string& type,
  239. base::Value::Dict user_info) {
  240. [[AtomApplication sharedApplication]
  241. updateCurrentActivity:base::SysUTF8ToNSString(type)
  242. withUserInfo:DictionaryValueToNSDictionary(
  243. std::move(user_info))];
  244. }
  245. bool Browser::WillContinueUserActivity(const std::string& type) {
  246. bool prevent_default = false;
  247. for (BrowserObserver& observer : observers_)
  248. observer.OnWillContinueUserActivity(&prevent_default, type);
  249. return prevent_default;
  250. }
  251. void Browser::DidFailToContinueUserActivity(const std::string& type,
  252. const std::string& error) {
  253. for (BrowserObserver& observer : observers_)
  254. observer.OnDidFailToContinueUserActivity(type, error);
  255. }
  256. bool Browser::ContinueUserActivity(const std::string& type,
  257. base::Value::Dict user_info,
  258. base::Value::Dict details) {
  259. bool prevent_default = false;
  260. for (BrowserObserver& observer : observers_)
  261. observer.OnContinueUserActivity(&prevent_default, type, user_info.Clone(),
  262. details.Clone());
  263. return prevent_default;
  264. }
  265. void Browser::UserActivityWasContinued(const std::string& type,
  266. base::Value::Dict user_info) {
  267. for (BrowserObserver& observer : observers_)
  268. observer.OnUserActivityWasContinued(type, user_info.Clone());
  269. }
  270. bool Browser::UpdateUserActivityState(const std::string& type,
  271. base::Value::Dict user_info) {
  272. bool prevent_default = false;
  273. for (BrowserObserver& observer : observers_)
  274. observer.OnUpdateUserActivityState(&prevent_default, type,
  275. user_info.Clone());
  276. return prevent_default;
  277. }
  278. // Modified from chrome/browser/ui/cocoa/l10n_util.mm.
  279. void Browser::ApplyForcedRTL() {
  280. NSUserDefaults* defaults = NSUserDefaults.standardUserDefaults;
  281. auto dir = base::i18n::GetForcedTextDirection();
  282. // An Electron app should respect RTL behavior of application locale over
  283. // system locale.
  284. auto should_be_rtl = dir == base::i18n::RIGHT_TO_LEFT || IsAppRTL();
  285. auto should_be_ltr = dir == base::i18n::LEFT_TO_RIGHT || !IsAppRTL();
  286. // -registerDefaults: won't do the trick here because these defaults exist
  287. // (in the global domain) to reflect the system locale. They need to be set
  288. // in Chrome's domain to supersede the system value.
  289. if (should_be_rtl) {
  290. [defaults setBool:YES forKey:@"AppleTextDirection"];
  291. [defaults setBool:YES forKey:@"NSForceRightToLeftWritingDirection"];
  292. } else if (should_be_ltr) {
  293. [defaults setBool:YES forKey:@"AppleTextDirection"];
  294. [defaults setBool:NO forKey:@"NSForceRightToLeftWritingDirection"];
  295. } else {
  296. [defaults removeObjectForKey:@"AppleTextDirection"];
  297. [defaults removeObjectForKey:@"NSForceRightToLeftWritingDirection"];
  298. }
  299. }
  300. Browser::LoginItemSettings Browser::GetLoginItemSettings(
  301. const LoginItemSettings& options) {
  302. LoginItemSettings settings;
  303. #if IS_MAS_BUILD()
  304. settings.open_at_login = platform_util::GetLoginItemEnabled();
  305. #else
  306. settings.open_at_login = CheckLoginItemStatus(&settings.open_as_hidden);
  307. settings.restore_state = base::mac::WasLaunchedAsLoginItemRestoreState();
  308. settings.opened_at_login = base::mac::WasLaunchedAsLoginOrResumeItem();
  309. settings.opened_as_hidden = base::mac::WasLaunchedAsHiddenLoginItem();
  310. #endif
  311. return settings;
  312. }
  313. void Browser::SetLoginItemSettings(LoginItemSettings settings) {
  314. #if IS_MAS_BUILD()
  315. if (!platform_util::SetLoginItemEnabled(settings.open_at_login)) {
  316. LOG(ERROR) << "Unable to set login item enabled on sandboxed app.";
  317. }
  318. #else
  319. if (settings.open_at_login) {
  320. base::mac::AddToLoginItems(base::mac::MainBundlePath(),
  321. settings.open_as_hidden);
  322. } else {
  323. base::mac::RemoveFromLoginItems(base::mac::MainBundlePath());
  324. }
  325. #endif
  326. }
  327. std::string Browser::GetExecutableFileVersion() const {
  328. return GetApplicationVersion();
  329. }
  330. std::string Browser::GetExecutableFileProductName() const {
  331. return GetApplicationName();
  332. }
  333. int Browser::DockBounce(BounceType type) {
  334. return [[AtomApplication sharedApplication]
  335. requestUserAttention:static_cast<NSRequestUserAttentionType>(type)];
  336. }
  337. void Browser::DockCancelBounce(int request_id) {
  338. [[AtomApplication sharedApplication] cancelUserAttentionRequest:request_id];
  339. }
  340. void Browser::DockSetBadgeText(const std::string& label) {
  341. NSDockTile* tile = [[AtomApplication sharedApplication] dockTile];
  342. [tile setBadgeLabel:base::SysUTF8ToNSString(label)];
  343. }
  344. void Browser::DockDownloadFinished(const std::string& filePath) {
  345. [[NSDistributedNotificationCenter defaultCenter]
  346. postNotificationName:@"com.apple.DownloadFileFinished"
  347. object:base::SysUTF8ToNSString(filePath)];
  348. }
  349. std::string Browser::DockGetBadgeText() {
  350. NSDockTile* tile = [[AtomApplication sharedApplication] dockTile];
  351. return base::SysNSStringToUTF8([tile badgeLabel]);
  352. }
  353. void Browser::DockHide() {
  354. // Transforming application state from UIElement to Foreground is an
  355. // asynchronous operation, and unfortunately there is currently no way to know
  356. // when it is finished.
  357. // So if we call DockHide => DockShow => DockHide => DockShow in a very short
  358. // time, we would trigger a bug of macOS that, there would be multiple dock
  359. // icons of the app left in system.
  360. // To work around this, we make sure DockHide does nothing if it is called
  361. // immediately after DockShow. After some experiments, 1 second seems to be
  362. // a proper interval.
  363. if (!last_dock_show_.is_null() &&
  364. base::Time::Now() - last_dock_show_ < base::Seconds(1)) {
  365. return;
  366. }
  367. for (auto* const& window : WindowList::GetWindows())
  368. [window->GetNativeWindow().GetNativeNSWindow() setCanHide:NO];
  369. ProcessSerialNumber psn = {0, kCurrentProcess};
  370. TransformProcessType(&psn, kProcessTransformToUIElementApplication);
  371. }
  372. bool Browser::DockIsVisible() {
  373. // Because DockShow has a slight delay this may not be true immediately
  374. // after that call.
  375. return ([[NSRunningApplication currentApplication] activationPolicy] ==
  376. NSApplicationActivationPolicyRegular);
  377. }
  378. v8::Local<v8::Promise> Browser::DockShow(v8::Isolate* isolate) {
  379. last_dock_show_ = base::Time::Now();
  380. gin_helper::Promise<void> promise(isolate);
  381. v8::Local<v8::Promise> handle = promise.GetHandle();
  382. BOOL active = [[NSRunningApplication currentApplication] isActive];
  383. ProcessSerialNumber psn = {0, kCurrentProcess};
  384. if (active) {
  385. // Workaround buggy behavior of TransformProcessType.
  386. // http://stackoverflow.com/questions/7596643/
  387. NSArray* runningApps = [NSRunningApplication
  388. runningApplicationsWithBundleIdentifier:@"com.apple.dock"];
  389. for (NSRunningApplication* app in runningApps) {
  390. [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
  391. break;
  392. }
  393. __block gin_helper::Promise<void> p = std::move(promise);
  394. dispatch_time_t one_ms = dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC);
  395. dispatch_after(one_ms, dispatch_get_main_queue(), ^{
  396. TransformProcessType(&psn, kProcessTransformToForegroundApplication);
  397. dispatch_time_t one_ms_2 = dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC);
  398. dispatch_after(one_ms_2, dispatch_get_main_queue(), ^{
  399. [[NSRunningApplication currentApplication]
  400. activateWithOptions:NSApplicationActivateIgnoringOtherApps];
  401. p.Resolve();
  402. });
  403. });
  404. } else {
  405. TransformProcessType(&psn, kProcessTransformToForegroundApplication);
  406. promise.Resolve();
  407. }
  408. return handle;
  409. }
  410. void Browser::DockSetMenu(ElectronMenuModel* model) {
  411. ElectronApplicationDelegate* delegate =
  412. (ElectronApplicationDelegate*)[NSApp delegate];
  413. [delegate setApplicationDockMenu:model];
  414. }
  415. void Browser::DockSetIcon(v8::Isolate* isolate, v8::Local<v8::Value> icon) {
  416. gfx::Image image;
  417. if (!icon->IsNull()) {
  418. api::NativeImage* native_image = nullptr;
  419. if (!api::NativeImage::TryConvertNativeImage(isolate, icon, &native_image))
  420. return;
  421. image = native_image->image();
  422. }
  423. // This is needed when this fn is called before the browser
  424. // process is ready, since supported scales are normally set
  425. // by ui::ResourceBundle::InitSharedInstance
  426. // during browser process startup.
  427. if (!is_ready())
  428. gfx::ImageSkia::SetSupportedScales({1.0f});
  429. [[AtomApplication sharedApplication]
  430. setApplicationIconImage:image.AsNSImage()];
  431. }
  432. void Browser::ShowAboutPanel() {
  433. NSDictionary* options = DictionaryValueToNSDictionary(about_panel_options_);
  434. // Credits must be a NSAttributedString instead of NSString
  435. NSString* credits = (NSString*)options[@"Credits"];
  436. if (credits != nil) {
  437. base::scoped_nsobject<NSMutableDictionary> mutable_options(
  438. [options mutableCopy]);
  439. base::scoped_nsobject<NSAttributedString> creditString(
  440. [[NSAttributedString alloc]
  441. initWithString:credits
  442. attributes:@{
  443. NSForegroundColorAttributeName : [NSColor textColor]
  444. }]);
  445. [mutable_options setValue:creditString forKey:@"Credits"];
  446. options = [NSDictionary dictionaryWithDictionary:mutable_options];
  447. }
  448. [[AtomApplication sharedApplication]
  449. orderFrontStandardAboutPanelWithOptions:options];
  450. }
  451. void Browser::SetAboutPanelOptions(base::Value::Dict options) {
  452. about_panel_options_.clear();
  453. for (const auto pair : options) {
  454. std::string key = pair.first;
  455. if (!key.empty() && pair.second.is_string()) {
  456. key[0] = base::ToUpperASCII(key[0]);
  457. about_panel_options_.Set(key, pair.second.Clone());
  458. }
  459. }
  460. }
  461. void Browser::ShowEmojiPanel() {
  462. [[AtomApplication sharedApplication] orderFrontCharacterPalette:nil];
  463. }
  464. bool Browser::IsEmojiPanelSupported() {
  465. return true;
  466. }
  467. bool Browser::IsSecureKeyboardEntryEnabled() {
  468. return password_input_enabler_.get() != nullptr;
  469. }
  470. void Browser::SetSecureKeyboardEntryEnabled(bool enabled) {
  471. if (enabled) {
  472. password_input_enabler_ =
  473. std::make_unique<ui::ScopedPasswordInputEnabler>();
  474. } else {
  475. password_input_enabler_.reset();
  476. }
  477. }
  478. } // namespace electron