Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsLookAndFeel.mm
blobd7650a6eb527983fa4862d4a88d430c79762262e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "AppearanceOverride.h"
7 #include "mozilla/widget/ThemeChangeKind.h"
8 #include "nsLookAndFeel.h"
9 #include "nsCocoaFeatures.h"
10 #include "nsNativeThemeColors.h"
11 #include "nsStyleConsts.h"
12 #include "nsIContent.h"
13 #include "gfxFont.h"
14 #include "gfxFontConstants.h"
15 #include "gfxPlatformMac.h"
16 #include "nsCSSColorUtils.h"
17 #include "mozilla/FontPropertyTypes.h"
18 #include "mozilla/gfx/2D.h"
19 #include "mozilla/StaticPrefs_widget.h"
20 #include "mozilla/Telemetry.h"
21 #include "mozilla/widget/WidgetMessageUtils.h"
23 #import <Cocoa/Cocoa.h>
24 #import <AppKit/NSColor.h>
26 // This must be included last:
27 #include "nsObjCExceptions.h"
29 using namespace mozilla;
31 @interface MOZLookAndFeelDynamicChangeObserver : NSObject
32 + (void)startObserving;
33 @end
35 nsLookAndFeel::nsLookAndFeel() {
36   [MOZLookAndFeelDynamicChangeObserver startObserving];
39 nsLookAndFeel::~nsLookAndFeel() = default;
41 void nsLookAndFeel::EnsureInit() {
42   if (mInitialized) {
43     return;
44   }
46   NS_OBJC_BEGIN_TRY_ABORT_BLOCK
48   mInitialized = true;
49   NSWindow* window =
50       [[NSWindow alloc] initWithContentRect:NSZeroRect
51                                   styleMask:NSWindowStyleMaskTitled
52                                     backing:NSBackingStoreBuffered
53                                       defer:NO];
54   auto release = MakeScopeExit([&] { [window release]; });
56   mRtl = window.windowTitlebarLayoutDirection ==
57          NSUserInterfaceLayoutDirectionRightToLeft;
58   mTitlebarHeight = std::ceil(window.frame.size.height);
60   RecordTelemetry();
62   NS_OBJC_END_TRY_ABORT_BLOCK
65 void nsLookAndFeel::RefreshImpl() {
66   mInitialized = false;
67   nsXPLookAndFeel::RefreshImpl();
70 static nscolor GetColorFromNSColor(NSColor* aColor) {
71   NSColor* deviceColor =
72       [aColor colorUsingColorSpace:NSColorSpace.deviceRGBColorSpace];
73   return NS_RGBA((unsigned int)(deviceColor.redComponent * 255.0),
74                  (unsigned int)(deviceColor.greenComponent * 255.0),
75                  (unsigned int)(deviceColor.blueComponent * 255.0),
76                  (unsigned int)(deviceColor.alphaComponent * 255.0));
79 static nscolor GetColorFromNSColorWithCustomAlpha(NSColor* aColor,
80                                                   float alpha) {
81   NSColor* deviceColor =
82       [aColor colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
83   return NS_RGBA((unsigned int)(deviceColor.redComponent * 255.0),
84                  (unsigned int)(deviceColor.greenComponent * 255.0),
85                  (unsigned int)(deviceColor.blueComponent * 255.0),
86                  (unsigned int)(alpha * 255.0));
89 // Turns an opaque selection color into a partially transparent selection color,
90 // which usually leads to better contrast with the text color and which should
91 // look more visually appealing in most contexts.
92 // The idea is that the text and its regular, non-selected background are
93 // usually chosen in such a way that they contrast well. Making the selection
94 // color partially transparent causes the selection color to mix with the text's
95 // regular background, so the end result will often have better contrast with
96 // the text than an arbitrary opaque selection color.
97 // The motivating example for this is the light selection color on dark web
98 // pages: White text on a light blue selection color has very bad contrast,
99 // whereas white text on dark blue (which what you get if you mix
100 // partially-transparent light blue with the black textbox background) has much
101 // better contrast.
102 static nscolor ProcessSelectionBackground(nscolor aColor, ColorScheme aScheme) {
103   if (aScheme == ColorScheme::Dark) {
104     // When we use a dark selection color, we do not change alpha because we do
105     // not use dark selection in content. The dark system color is appropriate
106     // for Firefox UI without needing to adjust its alpha.
107     return aColor;
108   }
109   uint16_t hue, sat, value;
110   uint8_t alpha;
111   nscolor resultColor = aColor;
112   NS_RGB2HSV(resultColor, hue, sat, value, alpha);
113   int factor = 2;
114   alpha = alpha / factor;
115   if (sat > 0) {
116     // The color is not a shade of grey, restore the saturation taken away by
117     // the transparency.
118     sat = mozilla::clamped(sat * factor, 0, 255);
119   } else {
120     // The color is a shade of grey, find the value that looks equivalent
121     // on a white background with the given opacity.
122     value = mozilla::clamped(255 - (255 - value) * factor, 0, 255);
123   }
124   NS_HSV2RGB(resultColor, hue, sat, value, alpha);
125   return resultColor;
128 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
129                                        nscolor& aColor) {
130   NS_OBJC_BEGIN_TRY_ABORT_BLOCK
132   NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
134   nscolor color = 0;
135   switch (aID) {
136     case ColorID::Infobackground:
137       color = aScheme == ColorScheme::Light
138                   ? NS_RGB(0xdd, 0xdd, 0xdd)
139                   : GetColorFromNSColor(NSColor.windowBackgroundColor);
140       break;
141     case ColorID::Highlight:
142       color = ProcessSelectionBackground(
143           GetColorFromNSColor(NSColor.selectedTextBackgroundColor), aScheme);
144       break;
145     // This is used to gray out the selection when it's not focused. Used with
146     // nsISelectionController::SELECTION_DISABLED.
147     case ColorID::TextSelectDisabledBackground:
148       color = ProcessSelectionBackground(
149           GetColorFromNSColor(NSColor.secondarySelectedControlColor), aScheme);
150       break;
151     case ColorID::MozMenuhoverdisabled:
152       aColor = NS_TRANSPARENT;
153       break;
154     case ColorID::Accentcolor:
155       color = GetColorFromNSColor(NSColor.controlAccentColor);
156       break;
157     case ColorID::MozMenuhover:
158     case ColorID::Selecteditem:
159       color = GetColorFromNSColor(NSColor.selectedContentBackgroundColor);
160       if (aID == ColorID::MozMenuhover &&
161           !LookAndFeel::GetInt(IntID::PrefersReducedTransparency)) {
162         // Wash the color a little bit with semi-transparent white to match a
163         // bit closer the native NSVisualEffectSelection on menus.
164         color = NS_ComposeColors(
165             color,
166             NS_RGBA(255, 255, 255, aScheme == ColorScheme::Light ? 51 : 25));
167       }
168       break;
169     case ColorID::Accentcolortext:
170     case ColorID::MozMenuhovertext:
171     case ColorID::Selecteditemtext:
172       color = GetColorFromNSColor(NSColor.selectedMenuItemTextColor);
173       break;
174     case ColorID::IMESelectedRawTextBackground:
175     case ColorID::IMESelectedConvertedTextBackground:
176     case ColorID::IMERawInputBackground:
177     case ColorID::IMEConvertedTextBackground:
178       color = NS_TRANSPARENT;
179       break;
180     case ColorID::IMESelectedRawTextForeground:
181     case ColorID::IMESelectedConvertedTextForeground:
182     case ColorID::IMERawInputForeground:
183     case ColorID::IMEConvertedTextForeground:
184     case ColorID::Highlighttext:
185       color = NS_SAME_AS_FOREGROUND_COLOR;
186       break;
187     case ColorID::IMERawInputUnderline:
188     case ColorID::IMEConvertedTextUnderline:
189       color = NS_40PERCENT_FOREGROUND_COLOR;
190       break;
191     case ColorID::IMESelectedRawTextUnderline:
192     case ColorID::IMESelectedConvertedTextUnderline:
193       color = NS_SAME_AS_FOREGROUND_COLOR;
194       break;
196       //
197       // css2 system colors http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
198       //
199       // It's really hard to effectively map these to the Appearance Manager
200       // properly, since they are modeled word for word after the win32 system
201       // colors and don't have any real counterparts in the Mac world. I'm sure
202       // we'll be tweaking these for years to come.
203       //
204       // Thanks to mpt26@student.canterbury.ac.nz for the hardcoded values that
205       // form the defaults
206       //  if querying the Appearance Manager fails ;)
207       //
208     case ColorID::MozMacDefaultbuttontext:
209       color = NS_RGB(0xFF, 0xFF, 0xFF);
210       break;
211     case ColorID::MozSidebar:
212       color = aScheme == ColorScheme::Light ? NS_RGB(0xf6, 0xf6, 0xf6)
213                                             : NS_RGB(0x2d, 0x2d, 0x2d);
214       break;
215     case ColorID::MozSidebarborder:
216       // hsla(240, 5%, 5%, .1)
217       color = NS_RGBA(12, 12, 13, 26);
218       break;
219     case ColorID::MozButtonactivetext:
220       // Pre-macOS 12, pressed buttons were filled with the highlight color and
221       // the text was white. Starting with macOS 12, pressed (non-default)
222       // buttons are filled with medium gray and the text color is the same as
223       // in the non-pressed state.
224       color = nsCocoaFeatures::OnMontereyOrLater()
225                   ? GetColorFromNSColor(NSColor.controlTextColor)
226                   : NS_RGB(0xFF, 0xFF, 0xFF);
227       break;
228     case ColorID::Windowtext:
229     case ColorID::MozDialogtext:
230       color = GetColorFromNSColor(NSColor.windowFrameTextColor);
231       break;
232     case ColorID::Appworkspace:
233       color = NS_RGB(0xFF, 0xFF, 0xFF);
234       break;
235     case ColorID::Background:
236       color = NS_RGB(0x63, 0x63, 0xCE);
237       break;
238     case ColorID::Buttonface:
239     case ColorID::MozButtonhoverface:
240     case ColorID::MozButtonactiveface:
241     case ColorID::MozButtondisabledface:
242     case ColorID::MozColheader:
243     case ColorID::MozColheaderhover:
244     case ColorID::MozColheaderactive:
245       color = GetColorFromNSColor(NSColor.controlColor);
246       if (!NS_GET_A(color)) {
247         color = GetColorFromNSColor(NSColor.controlBackgroundColor);
248       }
249       break;
250     case ColorID::Buttonhighlight:
251       color = GetColorFromNSColor(NSColor.selectedControlColor);
252       break;
253     case ColorID::Scrollbar:
254       color = GetColorFromNSColor(NSColor.scrollBarColor);
255       break;
256     case ColorID::Threedhighlight:
257       color = GetColorFromNSColor(NSColor.highlightColor);
258       break;
259     case ColorID::Buttonshadow:
260     case ColorID::Threeddarkshadow:
261       color = aScheme == ColorScheme::Dark ? *GenericDarkColor(aID)
262                                            : NS_RGB(0xDC, 0xDC, 0xDC);
263       break;
264     case ColorID::Threedshadow:
265       color = aScheme == ColorScheme::Dark ? *GenericDarkColor(aID)
266                                            : NS_RGB(0xE0, 0xE0, 0xE0);
267       break;
268     case ColorID::Threedface:
269       color = aScheme == ColorScheme::Dark ? *GenericDarkColor(aID)
270                                            : NS_RGB(0xF0, 0xF0, 0xF0);
271       break;
272     case ColorID::Threedlightshadow:
273     case ColorID::Buttonborder:
274     case ColorID::MozDisabledfield:
275       color = aScheme == ColorScheme::Dark ? *GenericDarkColor(aID)
276                                            : NS_RGB(0xDA, 0xDA, 0xDA);
277       break;
278     case ColorID::Menu:
279       // Hand-picked from Sonoma because there doesn't seem to be any
280       // appropriate menu system color.
281       color = aScheme == ColorScheme::Dark ? NS_RGB(0x36, 0x36, 0x39)
282                                            : NS_RGB(0xeb, 0xeb, 0xeb);
283       break;
284     case ColorID::Windowframe:
285       color = GetColorFromNSColor(NSColor.windowFrameColor);
286       break;
287     case ColorID::MozDialog:
288     case ColorID::Window:
289       color = GetColorFromNSColor(aScheme == ColorScheme::Light
290                                       ? NSColor.windowBackgroundColor
291                                       : NSColor.underPageBackgroundColor);
292       break;
293     case ColorID::Field:
294     case ColorID::MozCombobox:
295       color = GetColorFromNSColor(NSColor.controlBackgroundColor);
296       break;
297     case ColorID::Fieldtext:
298     case ColorID::MozComboboxtext:
299     case ColorID::Buttontext:
300     case ColorID::MozButtonhovertext:
301     case ColorID::Menutext:
302     case ColorID::Infotext:
303     case ColorID::MozCellhighlighttext:
304     case ColorID::MozColheadertext:
305     case ColorID::MozColheaderhovertext:
306     case ColorID::MozColheaderactivetext:
307     case ColorID::MozSidebartext:
308       color = GetColorFromNSColor(NSColor.controlTextColor);
309       break;
310     case ColorID::MozMacFocusring:
311       color = GetColorFromNSColorWithCustomAlpha(
312           NSColor.keyboardFocusIndicatorColor, 0.48);
313       break;
314     case ColorID::MozMacDisabledtoolbartext:
315     case ColorID::Graytext:
316       color = GetColorFromNSColor(NSColor.disabledControlTextColor);
317       break;
318     case ColorID::MozCellhighlight:
319       // For inactive list selection
320       color = GetColorFromNSColor(NSColor.secondarySelectedControlColor);
321       break;
322     case ColorID::MozEventreerow:
323       // Background color of even list rows.
324       color =
325           GetColorFromNSColor(NSColor.controlAlternatingRowBackgroundColors[0]);
326       break;
327     case ColorID::MozOddtreerow:
328       // Background color of odd list rows.
329       color =
330           GetColorFromNSColor(NSColor.controlAlternatingRowBackgroundColors[1]);
331       break;
332     case ColorID::MozNativehyperlinktext:
333       color = GetColorFromNSColor(NSColor.linkColor);
334       break;
335     case ColorID::MozNativevisitedhyperlinktext:
336       color = GetColorFromNSColor(NSColor.systemPurpleColor);
337       break;
338     case ColorID::MozHeaderbartext:
339     case ColorID::MozHeaderbarinactivetext:
340     case ColorID::Inactivecaptiontext:
341     case ColorID::Captiontext:
342       aColor = GetColorFromNSColor(NSColor.textColor);
343       return NS_OK;
344     case ColorID::MozHeaderbar:
345     case ColorID::MozHeaderbarinactive:
346     case ColorID::Inactivecaption:
347     case ColorID::Activecaption:
348       // This has better contrast than the stand-in colors.
349       aColor = GetColorFromNSColor(NSColor.windowBackgroundColor);
350       return NS_OK;
351     case ColorID::Marktext:
352     case ColorID::Mark:
353     case ColorID::SpellCheckerUnderline:
354     case ColorID::Activeborder:
355     case ColorID::Inactiveborder:
356       aColor = GetStandinForNativeColor(aID, aScheme);
357       return NS_OK;
358     default:
359       aColor = NS_RGB(0xff, 0xff, 0xff);
360       return NS_ERROR_FAILURE;
361   }
363   aColor = color;
364   return NS_OK;
366   NS_OBJC_END_TRY_ABORT_BLOCK
369 static bool SystemWantsDarkTheme() {
370   // This returns true if the macOS system appearance is set to dark mode,
371   // false otherwise.
372   NSAppearanceName aquaOrDarkAqua =
373       [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[
374         NSAppearanceNameAqua, NSAppearanceNameDarkAqua
375       ]];
376   return [aquaOrDarkAqua isEqualToString:NSAppearanceNameDarkAqua];
379 nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
380   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
382   nsresult res = NS_OK;
384   switch (aID) {
385     case IntID::ScrollButtonLeftMouseButtonAction:
386       aResult = 0;
387       break;
388     case IntID::ScrollButtonMiddleMouseButtonAction:
389     case IntID::ScrollButtonRightMouseButtonAction:
390       aResult = 3;
391       break;
392     case IntID::CaretBlinkTime:
393       aResult = 567;
394       break;
395     case IntID::CaretWidth:
396       aResult = 1;
397       break;
398     case IntID::SelectTextfieldsOnKeyFocus:
399       // Select textfield content when focused by kbd
400       // used by EventStateManager::sTextfieldSelectModel
401       aResult = 1;
402       break;
403     case IntID::SubmenuDelay:
404       aResult = 200;
405       break;
406     case IntID::MenusCanOverlapOSBar:
407       // xul popups are not allowed to overlap the menubar.
408       aResult = 0;
409       break;
410     case IntID::SkipNavigatingDisabledMenuItem:
411       aResult = 1;
412       break;
413     case IntID::DragThresholdX:
414     case IntID::DragThresholdY:
415       aResult = 4;
416       break;
417     case IntID::ScrollArrowStyle:
418       aResult = eScrollArrow_None;
419       break;
420     case IntID::UseOverlayScrollbars:
421     case IntID::AllowOverlayScrollbarsOverlap:
422       aResult = NSScroller.preferredScrollerStyle == NSScrollerStyleOverlay;
423       break;
424     case IntID::ScrollbarDisplayOnMouseMove:
425       aResult = 0;
426       break;
427     case IntID::ScrollbarFadeBeginDelay:
428       aResult = 450;
429       break;
430     case IntID::ScrollbarFadeDuration:
431       aResult = 200;
432       break;
433     case IntID::TreeOpenDelay:
434       aResult = 1000;
435       break;
436     case IntID::TreeCloseDelay:
437       aResult = 1000;
438       break;
439     case IntID::TreeLazyScrollDelay:
440       aResult = 150;
441       break;
442     case IntID::TreeScrollDelay:
443       aResult = 100;
444       break;
445     case IntID::TreeScrollLinesMax:
446       aResult = 3;
447       break;
448     case IntID::MacBigSurTheme:
449       aResult = nsCocoaFeatures::OnBigSurOrLater();
450       break;
451     case IntID::MacRTL:
452       EnsureInit();
453       aResult = mRtl;
454       break;
455     case IntID::MacTitlebarHeight:
456       EnsureInit();
457       aResult = mTitlebarHeight;
458       break;
459     case IntID::AlertNotificationOrigin:
460       aResult = NS_ALERT_TOP;
461       break;
462     case IntID::TabFocusModel:
463       aResult = [NSApp isFullKeyboardAccessEnabled]
464                     ? nsIContent::eTabFocus_any
465                     : nsIContent::eTabFocus_textControlsMask;
466       break;
467     case IntID::ScrollToClick: {
468       aResult = [[NSUserDefaults standardUserDefaults]
469           boolForKey:@"AppleScrollerPagingBehavior"];
470     } break;
471     case IntID::ChosenMenuItemsShouldBlink:
472       aResult = 1;
473       break;
474     case IntID::IMERawInputUnderlineStyle:
475     case IntID::IMEConvertedTextUnderlineStyle:
476     case IntID::IMESelectedRawTextUnderlineStyle:
477     case IntID::IMESelectedConvertedTextUnderline:
478       aResult = static_cast<int32_t>(StyleTextDecorationStyle::Solid);
479       break;
480     case IntID::SpellCheckerUnderlineStyle:
481       aResult = static_cast<int32_t>(StyleTextDecorationStyle::Dotted);
482       break;
483     case IntID::ScrollbarButtonAutoRepeatBehavior:
484       aResult = 0;
485       break;
486     case IntID::SwipeAnimationEnabled:
487       aResult = NSEvent.isSwipeTrackingFromScrollEventsEnabled;
488       break;
489     case IntID::ContextMenuOffsetVertical:
490       aResult = -6;
491       break;
492     case IntID::ContextMenuOffsetHorizontal:
493       aResult = 1;
494       break;
495     case IntID::SystemUsesDarkTheme:
496       aResult = SystemWantsDarkTheme();
497       break;
498     case IntID::PrefersReducedMotion:
499       aResult =
500           NSWorkspace.sharedWorkspace.accessibilityDisplayShouldReduceMotion;
501       break;
502     case IntID::PrefersReducedTransparency:
503       aResult = NSWorkspace.sharedWorkspace
504                     .accessibilityDisplayShouldReduceTransparency;
505       break;
506     case IntID::InvertedColors:
507       aResult =
508           NSWorkspace.sharedWorkspace.accessibilityDisplayShouldInvertColors;
509       break;
510     case IntID::UseAccessibilityTheme:
511       aResult = NSWorkspace.sharedWorkspace
512                     .accessibilityDisplayShouldIncreaseContrast;
513       break;
514     case IntID::PanelAnimations:
515       aResult = 1;
516       break;
517     default:
518       aResult = 0;
519       res = NS_ERROR_FAILURE;
520   }
521   return res;
523   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
526 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
527   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
529   nsresult res = NS_OK;
531   switch (aID) {
532     case FloatID::IMEUnderlineRelativeSize:
533       aResult = 2.0f;
534       break;
535     case FloatID::SpellCheckerUnderlineRelativeSize:
536       aResult = 2.0f;
537       break;
538     case FloatID::CursorScale: {
539       id uaDefaults = [[NSUserDefaults alloc]
540           initWithSuiteName:@"com.apple.universalaccess"];
541       float f = [uaDefaults floatForKey:@"mouseDriverCursorSize"];
542       [uaDefaults release];
543       aResult = f > 0.0 ? f : 1.0;  // default to 1.0 if value not available
544       break;
545     }
546     default:
547       aResult = -1.0;
548       res = NS_ERROR_FAILURE;
549   }
551   return res;
553   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
556 bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
557                                   gfxFontStyle& aFontStyle) {
558   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
560   nsAutoCString name;
561   gfxPlatformMac::LookupSystemFont(aID, name, aFontStyle);
562   aFontName.Append(NS_ConvertUTF8toUTF16(name));
564   return true;
566   NS_OBJC_END_TRY_BLOCK_RETURN(false);
569 void nsLookAndFeel::RecordAccessibilityTelemetry() {
570   if ([[NSWorkspace sharedWorkspace]
571           respondsToSelector:@selector
572           (accessibilityDisplayShouldInvertColors)]) {
573     bool val =
574         [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors];
575     Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INVERT_COLORS, val);
576   }
579 @implementation MOZLookAndFeelDynamicChangeObserver
581 + (void)startObserving {
582   static MOZLookAndFeelDynamicChangeObserver* gInstance = nil;
583   if (!gInstance) {
584     gInstance = [[MOZLookAndFeelDynamicChangeObserver alloc] init];  // leaked
585   }
588 - (instancetype)init {
589   self = [super init];
591   [NSNotificationCenter.defaultCenter
592       addObserver:self
593          selector:@selector(colorsChanged)
594              name:NSControlTintDidChangeNotification
595            object:nil];
596   [NSNotificationCenter.defaultCenter
597       addObserver:self
598          selector:@selector(colorsChanged)
599              name:NSSystemColorsDidChangeNotification
600            object:nil];
602   [NSWorkspace.sharedWorkspace.notificationCenter
603       addObserver:self
604          selector:@selector(mediaQueriesChanged)
605              name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification
606            object:nil];
608   [NSNotificationCenter.defaultCenter
609       addObserver:self
610          selector:@selector(scrollbarsChanged)
611              name:NSPreferredScrollerStyleDidChangeNotification
612            object:nil];
613   [NSDistributedNotificationCenter.defaultCenter
614              addObserver:self
615                 selector:@selector(scrollbarsChanged)
616                     name:@"AppleAquaScrollBarVariantChanged"
617                   object:nil
618       suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
619   [NSDistributedNotificationCenter.defaultCenter
620              addObserver:self
621                 selector:@selector(cachedValuesChanged)
622                     name:@"AppleNoRedisplayAppearancePreferenceChanged"
623                   object:nil
624       suspensionBehavior:NSNotificationSuspensionBehaviorCoalesce];
625   [NSDistributedNotificationCenter.defaultCenter
626              addObserver:self
627                 selector:@selector(cachedValuesChanged)
628                     name:@"com.apple.KeyboardUIModeDidChange"
629                   object:nil
630       suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
632   [MOZGlobalAppearance.sharedInstance addObserver:self
633                                        forKeyPath:@"effectiveAppearance"
634                                           options:0
635                                           context:nil];
636   [NSApp addObserver:self
637           forKeyPath:@"effectiveAppearance"
638              options:0
639              context:nil];
641   return self;
644 - (void)observeValueForKeyPath:(NSString*)keyPath
645                       ofObject:(id)object
646                         change:(NSDictionary<NSKeyValueChangeKey, id>*)change
647                        context:(void*)context {
648   if ([keyPath isEqualToString:@"effectiveAppearance"]) {
649     [self entireThemeChanged];
650   } else {
651     [super observeValueForKeyPath:keyPath
652                          ofObject:object
653                            change:change
654                           context:context];
655   }
658 - (void)entireThemeChanged {
659   LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
662 - (void)scrollbarsChanged {
663   LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
666 - (void)mediaQueriesChanged {
667   // Changing`Invert Colors` sends
668   // AccessibilityDisplayOptionsDidChangeNotifications. We monitor that setting
669   // via telemetry, so call into that recording method here.
670   nsLookAndFeel::RecordAccessibilityTelemetry();
671   LookAndFeel::NotifyChangedAllWindows(
672       widget::ThemeChangeKind::MediaQueriesOnly);
675 - (void)colorsChanged {
676   LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style);
679 - (void)cachedValuesChanged {
680   // We only need to re-cache (and broadcast) updated LookAndFeel values, so
681   // that they're up-to-date the next time they're queried. No further change
682   // handling is needed.
683   // TODO: Add a change hint for this which avoids the unnecessary media query
684   // invalidation.
685   LookAndFeel::NotifyChangedAllWindows(
686       widget::ThemeChangeKind::MediaQueriesOnly);
688 @end