browser_mac.mm 17 KB

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