browser_mac.mm 16 KB

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