electron_api_system_preferences_mac.mm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. // Copyright (c) 2016 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/api/electron_api_system_preferences.h"
  5. #include <string>
  6. #include <utility>
  7. #import <AVFoundation/AVFoundation.h>
  8. #import <Cocoa/Cocoa.h>
  9. #import <LocalAuthentication/LocalAuthentication.h>
  10. #import <Security/Security.h>
  11. #include "base/apple/scoped_cftyperef.h"
  12. #include "base/containers/flat_map.h"
  13. #include "base/strings/sys_string_conversions.h"
  14. #include "base/task/sequenced_task_runner.h"
  15. #include "base/values.h"
  16. #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
  17. #include "net/base/apple/url_conversions.h"
  18. #include "shell/browser/mac/dict_util.h"
  19. #include "shell/browser/mac/electron_application.h"
  20. #include "shell/common/color_util.h"
  21. #include "shell/common/gin_converters/gurl_converter.h"
  22. #include "shell/common/gin_converters/value_converter.h"
  23. #include "shell/common/node_includes.h"
  24. #include "shell/common/process_util.h"
  25. #include "skia/ext/skia_utils_mac.h"
  26. namespace gin {
  27. template <>
  28. struct Converter<NSAppearance*> {
  29. static bool FromV8(v8::Isolate* isolate,
  30. v8::Local<v8::Value> val,
  31. NSAppearance* __strong* out) {
  32. if (val->IsNull()) {
  33. *out = nil;
  34. return true;
  35. }
  36. std::string name;
  37. if (!gin::ConvertFromV8(isolate, val, &name)) {
  38. return false;
  39. }
  40. if (name == "light") {
  41. *out = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
  42. return true;
  43. } else if (name == "dark") {
  44. *out = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
  45. return true;
  46. }
  47. return false;
  48. }
  49. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, NSAppearance* val) {
  50. if (val == nil)
  51. return v8::Null(isolate);
  52. if ([val.name isEqualToString:NSAppearanceNameAqua]) {
  53. return gin::ConvertToV8(isolate, "light");
  54. } else if ([val.name isEqualToString:NSAppearanceNameDarkAqua]) {
  55. return gin::ConvertToV8(isolate, "dark");
  56. }
  57. return gin::ConvertToV8(isolate, "unknown");
  58. }
  59. };
  60. } // namespace gin
  61. namespace electron::api {
  62. namespace {
  63. int g_next_id = 0;
  64. // The map to convert |id| to |int|.
  65. base::flat_map<int, id> g_id_map;
  66. AVMediaType ParseMediaType(const std::string& media_type) {
  67. if (media_type == "camera") {
  68. return AVMediaTypeVideo;
  69. } else if (media_type == "microphone") {
  70. return AVMediaTypeAudio;
  71. } else {
  72. return nil;
  73. }
  74. }
  75. std::string ConvertSystemPermission(
  76. system_media_permissions::SystemPermission value) {
  77. using SystemPermission = system_media_permissions::SystemPermission;
  78. switch (value) {
  79. case SystemPermission::kNotDetermined:
  80. return "not-determined";
  81. case SystemPermission::kRestricted:
  82. return "restricted";
  83. case SystemPermission::kDenied:
  84. return "denied";
  85. case SystemPermission::kAllowed:
  86. return "granted";
  87. default:
  88. return "unknown";
  89. }
  90. }
  91. NSNotificationCenter* GetNotificationCenter(NotificationCenterKind kind) {
  92. switch (kind) {
  93. case NotificationCenterKind::kNSDistributedNotificationCenter:
  94. return [NSDistributedNotificationCenter defaultCenter];
  95. case NotificationCenterKind::kNSNotificationCenter:
  96. return [NSNotificationCenter defaultCenter];
  97. case NotificationCenterKind::kNSWorkspaceNotificationCenter:
  98. return [[NSWorkspace sharedWorkspace] notificationCenter];
  99. default:
  100. return nil;
  101. }
  102. }
  103. } // namespace
  104. void SystemPreferences::PostNotification(const std::string& name,
  105. base::Value::Dict user_info,
  106. gin::Arguments* args) {
  107. bool immediate = false;
  108. args->GetNext(&immediate);
  109. NSDistributedNotificationCenter* center =
  110. [NSDistributedNotificationCenter defaultCenter];
  111. [center
  112. postNotificationName:base::SysUTF8ToNSString(name)
  113. object:nil
  114. userInfo:DictionaryValueToNSDictionary(std::move(user_info))
  115. deliverImmediately:immediate];
  116. }
  117. int SystemPreferences::SubscribeNotification(
  118. v8::Local<v8::Value> maybe_name,
  119. const NotificationCallback& callback) {
  120. return DoSubscribeNotification(
  121. maybe_name, callback,
  122. NotificationCenterKind::kNSDistributedNotificationCenter);
  123. }
  124. void SystemPreferences::UnsubscribeNotification(int request_id) {
  125. DoUnsubscribeNotification(
  126. request_id, NotificationCenterKind::kNSDistributedNotificationCenter);
  127. }
  128. void SystemPreferences::PostLocalNotification(const std::string& name,
  129. base::Value::Dict user_info) {
  130. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  131. [center
  132. postNotificationName:base::SysUTF8ToNSString(name)
  133. object:nil
  134. userInfo:DictionaryValueToNSDictionary(std::move(user_info))];
  135. }
  136. int SystemPreferences::SubscribeLocalNotification(
  137. v8::Local<v8::Value> maybe_name,
  138. const NotificationCallback& callback) {
  139. return DoSubscribeNotification(maybe_name, callback,
  140. NotificationCenterKind::kNSNotificationCenter);
  141. }
  142. void SystemPreferences::UnsubscribeLocalNotification(int request_id) {
  143. DoUnsubscribeNotification(request_id,
  144. NotificationCenterKind::kNSNotificationCenter);
  145. }
  146. void SystemPreferences::PostWorkspaceNotification(const std::string& name,
  147. base::Value::Dict user_info) {
  148. NSNotificationCenter* center =
  149. [[NSWorkspace sharedWorkspace] notificationCenter];
  150. [center
  151. postNotificationName:base::SysUTF8ToNSString(name)
  152. object:nil
  153. userInfo:DictionaryValueToNSDictionary(std::move(user_info))];
  154. }
  155. int SystemPreferences::SubscribeWorkspaceNotification(
  156. v8::Local<v8::Value> maybe_name,
  157. const NotificationCallback& callback) {
  158. return DoSubscribeNotification(
  159. maybe_name, callback,
  160. NotificationCenterKind::kNSWorkspaceNotificationCenter);
  161. }
  162. void SystemPreferences::UnsubscribeWorkspaceNotification(int request_id) {
  163. DoUnsubscribeNotification(
  164. request_id, NotificationCenterKind::kNSWorkspaceNotificationCenter);
  165. }
  166. int SystemPreferences::DoSubscribeNotification(
  167. v8::Local<v8::Value> maybe_name,
  168. const NotificationCallback& callback,
  169. NotificationCenterKind kind) {
  170. int request_id = g_next_id++;
  171. __block NotificationCallback copied_callback = callback;
  172. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  173. std::string name_str;
  174. if (!(maybe_name->IsNull() ||
  175. gin::ConvertFromV8(isolate, maybe_name, &name_str))) {
  176. isolate->ThrowException(v8::Exception::Error(
  177. gin::StringToV8(isolate, "Must pass null or a string")));
  178. return -1;
  179. }
  180. auto* name = maybe_name->IsNull() ? nil : base::SysUTF8ToNSString(name_str);
  181. g_id_map[request_id] = [GetNotificationCenter(kind)
  182. addObserverForName:name
  183. object:nil
  184. queue:nil
  185. usingBlock:^(NSNotification* notification) {
  186. std::string object = "";
  187. if ([notification.object isKindOfClass:[NSString class]]) {
  188. object = base::SysNSStringToUTF8(notification.object);
  189. }
  190. if (notification.userInfo) {
  191. copied_callback.Run(
  192. base::SysNSStringToUTF8(notification.name),
  193. base::Value(NSDictionaryToValue(notification.userInfo)),
  194. object);
  195. } else {
  196. copied_callback.Run(
  197. base::SysNSStringToUTF8(notification.name),
  198. base::Value(base::Value::Dict()), object);
  199. }
  200. }];
  201. return request_id;
  202. }
  203. void SystemPreferences::DoUnsubscribeNotification(int request_id,
  204. NotificationCenterKind kind) {
  205. auto iter = g_id_map.find(request_id);
  206. if (iter != g_id_map.end()) {
  207. id observer = iter->second;
  208. [GetNotificationCenter(kind) removeObserver:observer];
  209. g_id_map.erase(iter);
  210. }
  211. }
  212. v8::Local<v8::Value> SystemPreferences::GetUserDefault(
  213. v8::Isolate* isolate,
  214. const std::string& name,
  215. const std::string& type) {
  216. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  217. NSString* key = base::SysUTF8ToNSString(name);
  218. if (type == "string") {
  219. return gin::StringToV8(
  220. isolate, base::SysNSStringToUTF8([defaults stringForKey:key]));
  221. } else if (type == "boolean") {
  222. return v8::Boolean::New(isolate, [defaults boolForKey:key]);
  223. } else if (type == "float") {
  224. return v8::Number::New(isolate, [defaults floatForKey:key]);
  225. } else if (type == "integer") {
  226. return v8::Integer::New(isolate, [defaults integerForKey:key]);
  227. } else if (type == "double") {
  228. return v8::Number::New(isolate, [defaults doubleForKey:key]);
  229. } else if (type == "url") {
  230. return gin::ConvertToV8(isolate,
  231. net::GURLWithNSURL([defaults URLForKey:key]));
  232. } else if (type == "array") {
  233. return gin::ConvertToV8(
  234. isolate, base::Value(NSArrayToValue([defaults arrayForKey:key])));
  235. } else if (type == "dictionary") {
  236. return gin::ConvertToV8(
  237. isolate,
  238. base::Value(NSDictionaryToValue([defaults dictionaryForKey:key])));
  239. } else {
  240. return v8::Undefined(isolate);
  241. }
  242. }
  243. void SystemPreferences::RegisterDefaults(gin::Arguments* args) {
  244. base::Value::Dict dict_value;
  245. if (!args->GetNext(&dict_value)) {
  246. args->ThrowError();
  247. return;
  248. }
  249. @try {
  250. NSDictionary* dict = DictionaryValueToNSDictionary(std::move(dict_value));
  251. for (id key in dict) {
  252. id value = [dict objectForKey:key];
  253. if ([value isKindOfClass:[NSNull class]] || value == nil) {
  254. args->ThrowError();
  255. return;
  256. }
  257. }
  258. [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
  259. } @catch (NSException* exception) {
  260. args->ThrowError();
  261. }
  262. }
  263. void SystemPreferences::SetUserDefault(const std::string& name,
  264. const std::string& type,
  265. gin::Arguments* args) {
  266. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  267. NSString* key = base::SysUTF8ToNSString(name);
  268. if (type == "string") {
  269. std::string value;
  270. if (args->GetNext(&value)) {
  271. [defaults setObject:base::SysUTF8ToNSString(value) forKey:key];
  272. return;
  273. }
  274. } else if (type == "boolean") {
  275. bool value;
  276. v8::Local<v8::Value> next = args->PeekNext();
  277. if (!next.IsEmpty() && next->IsBoolean() && args->GetNext(&value)) {
  278. [defaults setBool:value forKey:key];
  279. return;
  280. }
  281. } else if (type == "float") {
  282. float value;
  283. if (args->GetNext(&value)) {
  284. [defaults setFloat:value forKey:key];
  285. return;
  286. }
  287. } else if (type == "integer") {
  288. int value;
  289. if (args->GetNext(&value)) {
  290. [defaults setInteger:value forKey:key];
  291. return;
  292. }
  293. } else if (type == "double") {
  294. double value;
  295. if (args->GetNext(&value)) {
  296. [defaults setDouble:value forKey:key];
  297. return;
  298. }
  299. } else if (type == "url") {
  300. GURL value;
  301. if (args->GetNext(&value)) {
  302. if (NSURL* url = net::NSURLWithGURL(value)) {
  303. [defaults setURL:url forKey:key];
  304. return;
  305. }
  306. }
  307. } else if (type == "array") {
  308. base::Value value;
  309. if (args->GetNext(&value) && value.is_list()) {
  310. if (NSArray* array = ListValueToNSArray(value.GetList())) {
  311. [defaults setObject:array forKey:key];
  312. return;
  313. }
  314. }
  315. } else if (type == "dictionary") {
  316. base::Value value;
  317. if (args->GetNext(&value) && value.is_dict()) {
  318. if (NSDictionary* dict = DictionaryValueToNSDictionary(value.GetDict())) {
  319. [defaults setObject:dict forKey:key];
  320. return;
  321. }
  322. }
  323. } else {
  324. gin_helper::ErrorThrower(args->isolate())
  325. .ThrowTypeError("Invalid type: " + type);
  326. return;
  327. }
  328. gin_helper::ErrorThrower(args->isolate())
  329. .ThrowTypeError("Unable to convert value to: " + type);
  330. }
  331. std::string SystemPreferences::GetAccentColor() {
  332. NSColor* sysColor = sysColor = [NSColor controlAccentColor];
  333. return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor),
  334. false /* include_hash */);
  335. }
  336. std::string SystemPreferences::GetSystemColor(gin_helper::ErrorThrower thrower,
  337. const std::string& color) {
  338. NSColor* sysColor = nil;
  339. if (color == "blue") {
  340. sysColor = [NSColor systemBlueColor];
  341. } else if (color == "brown") {
  342. sysColor = [NSColor systemBrownColor];
  343. } else if (color == "gray") {
  344. sysColor = [NSColor systemGrayColor];
  345. } else if (color == "green") {
  346. sysColor = [NSColor systemGreenColor];
  347. } else if (color == "orange") {
  348. sysColor = [NSColor systemOrangeColor];
  349. } else if (color == "pink") {
  350. sysColor = [NSColor systemPinkColor];
  351. } else if (color == "purple") {
  352. sysColor = [NSColor systemPurpleColor];
  353. } else if (color == "red") {
  354. sysColor = [NSColor systemRedColor];
  355. } else if (color == "yellow") {
  356. sysColor = [NSColor systemYellowColor];
  357. } else {
  358. thrower.ThrowError("Unknown system color: " + color);
  359. return "";
  360. }
  361. return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor));
  362. }
  363. bool SystemPreferences::CanPromptTouchID() {
  364. LAContext* context = [[LAContext alloc] init];
  365. LAPolicy auth_policy = LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch;
  366. if (![context canEvaluatePolicy:auth_policy error:nil])
  367. return false;
  368. return [context biometryType] == LABiometryTypeTouchID;
  369. }
  370. v8::Local<v8::Promise> SystemPreferences::PromptTouchID(
  371. v8::Isolate* isolate,
  372. const std::string& reason) {
  373. gin_helper::Promise<void> promise(isolate);
  374. v8::Local<v8::Promise> handle = promise.GetHandle();
  375. LAContext* context = [[LAContext alloc] init];
  376. base::apple::ScopedCFTypeRef<SecAccessControlRef> access_control =
  377. base::apple::ScopedCFTypeRef<SecAccessControlRef>(
  378. SecAccessControlCreateWithFlags(
  379. kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
  380. kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence,
  381. nullptr));
  382. scoped_refptr<base::SequencedTaskRunner> runner =
  383. base::SequencedTaskRunner::GetCurrentDefault();
  384. __block gin_helper::Promise<void> p = std::move(promise);
  385. [context
  386. evaluateAccessControl:access_control.get()
  387. operation:LAAccessControlOperationUseKeySign
  388. localizedReason:[NSString stringWithUTF8String:reason.c_str()]
  389. reply:^(BOOL success, NSError* error) {
  390. // NOLINTBEGIN(bugprone-use-after-move)
  391. if (!success) {
  392. std::string err_msg = std::string(
  393. [error.localizedDescription UTF8String]);
  394. runner->PostTask(
  395. FROM_HERE,
  396. base::BindOnce(
  397. gin_helper::Promise<void>::RejectPromise,
  398. std::move(p), std::move(err_msg)));
  399. } else {
  400. runner->PostTask(
  401. FROM_HERE,
  402. base::BindOnce(
  403. gin_helper::Promise<void>::ResolvePromise,
  404. std::move(p)));
  405. }
  406. // NOLINTEND(bugprone-use-after-move)
  407. }];
  408. return handle;
  409. }
  410. // static
  411. bool SystemPreferences::IsTrustedAccessibilityClient(bool prompt) {
  412. NSDictionary* options =
  413. @{(__bridge id)kAXTrustedCheckOptionPrompt : @(prompt)};
  414. return AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
  415. }
  416. std::string SystemPreferences::GetColor(gin_helper::ErrorThrower thrower,
  417. const std::string& color) {
  418. NSColor* sysColor = nil;
  419. if (color == "control-background") {
  420. sysColor = [NSColor controlBackgroundColor];
  421. } else if (color == "control") {
  422. sysColor = [NSColor controlColor];
  423. } else if (color == "control-text") {
  424. sysColor = [NSColor controlTextColor];
  425. } else if (color == "disabled-control-text") {
  426. sysColor = [NSColor disabledControlTextColor];
  427. } else if (color == "find-highlight") {
  428. sysColor = [NSColor findHighlightColor];
  429. } else if (color == "grid") {
  430. sysColor = [NSColor gridColor];
  431. } else if (color == "header-text") {
  432. sysColor = [NSColor headerTextColor];
  433. } else if (color == "highlight") {
  434. sysColor = [NSColor highlightColor];
  435. } else if (color == "keyboard-focus-indicator") {
  436. sysColor = [NSColor keyboardFocusIndicatorColor];
  437. } else if (color == "label") {
  438. sysColor = [NSColor labelColor];
  439. } else if (color == "link") {
  440. sysColor = [NSColor linkColor];
  441. } else if (color == "placeholder-text") {
  442. sysColor = [NSColor placeholderTextColor];
  443. } else if (color == "quaternary-label") {
  444. sysColor = [NSColor quaternaryLabelColor];
  445. } else if (color == "scrubber-textured-background") {
  446. sysColor = [NSColor scrubberTexturedBackgroundColor];
  447. } else if (color == "secondary-label") {
  448. sysColor = [NSColor secondaryLabelColor];
  449. } else if (color == "selected-content-background") {
  450. sysColor = [NSColor selectedContentBackgroundColor];
  451. } else if (color == "selected-control") {
  452. sysColor = [NSColor selectedControlColor];
  453. } else if (color == "selected-control-text") {
  454. sysColor = [NSColor selectedControlTextColor];
  455. } else if (color == "selected-menu-item-text") {
  456. sysColor = [NSColor selectedMenuItemTextColor];
  457. } else if (color == "selected-text-background") {
  458. sysColor = [NSColor selectedTextBackgroundColor];
  459. } else if (color == "selected-text") {
  460. sysColor = [NSColor selectedTextColor];
  461. } else if (color == "separator") {
  462. sysColor = [NSColor separatorColor];
  463. } else if (color == "shadow") {
  464. sysColor = [NSColor shadowColor];
  465. } else if (color == "tertiary-label") {
  466. sysColor = [NSColor tertiaryLabelColor];
  467. } else if (color == "text-background") {
  468. sysColor = [NSColor textBackgroundColor];
  469. } else if (color == "text") {
  470. sysColor = [NSColor textColor];
  471. } else if (color == "under-page-background") {
  472. sysColor = [NSColor underPageBackgroundColor];
  473. } else if (color == "unemphasized-selected-content-background") {
  474. sysColor = [NSColor unemphasizedSelectedContentBackgroundColor];
  475. } else if (color == "unemphasized-selected-text-background") {
  476. sysColor = [NSColor unemphasizedSelectedTextBackgroundColor];
  477. } else if (color == "unemphasized-selected-text") {
  478. sysColor = [NSColor unemphasizedSelectedTextColor];
  479. } else if (color == "window-background") {
  480. sysColor = [NSColor windowBackgroundColor];
  481. } else if (color == "window-frame-text") {
  482. sysColor = [NSColor windowFrameTextColor];
  483. } else {
  484. thrower.ThrowError("Unknown color: " + color);
  485. }
  486. if (sysColor)
  487. return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor));
  488. return "";
  489. }
  490. std::string SystemPreferences::GetMediaAccessStatus(
  491. gin_helper::ErrorThrower thrower,
  492. const std::string& media_type) {
  493. if (media_type == "camera") {
  494. return ConvertSystemPermission(
  495. system_media_permissions::CheckSystemVideoCapturePermission());
  496. } else if (media_type == "microphone") {
  497. return ConvertSystemPermission(
  498. system_media_permissions::CheckSystemAudioCapturePermission());
  499. } else if (media_type == "screen") {
  500. return ConvertSystemPermission(
  501. system_media_permissions::CheckSystemScreenCapturePermission());
  502. } else {
  503. thrower.ThrowError("Invalid media type");
  504. return std::string();
  505. }
  506. }
  507. v8::Local<v8::Promise> SystemPreferences::AskForMediaAccess(
  508. v8::Isolate* isolate,
  509. const std::string& media_type) {
  510. gin_helper::Promise<bool> promise(isolate);
  511. v8::Local<v8::Promise> handle = promise.GetHandle();
  512. if (auto type = ParseMediaType(media_type)) {
  513. __block gin_helper::Promise<bool> p = std::move(promise);
  514. [AVCaptureDevice requestAccessForMediaType:type
  515. completionHandler:^(BOOL granted) {
  516. dispatch_async(dispatch_get_main_queue(), ^{
  517. p.Resolve(!!granted);
  518. });
  519. }];
  520. } else {
  521. promise.RejectWithErrorMessage("Invalid media type");
  522. }
  523. return handle;
  524. }
  525. void SystemPreferences::RemoveUserDefault(const std::string& name) {
  526. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  527. [defaults removeObjectForKey:base::SysUTF8ToNSString(name)];
  528. }
  529. bool SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled() {
  530. return [NSEvent isSwipeTrackingFromScrollEventsEnabled];
  531. }
  532. v8::Local<v8::Value> SystemPreferences::GetEffectiveAppearance(
  533. v8::Isolate* isolate) {
  534. return gin::ConvertToV8(
  535. isolate, [NSApplication sharedApplication].effectiveAppearance);
  536. }
  537. bool SystemPreferences::AccessibilityDisplayShouldReduceTransparency() {
  538. return [[NSWorkspace sharedWorkspace]
  539. accessibilityDisplayShouldReduceTransparency];
  540. }
  541. } // namespace electron::api