File: screen_mac.mm

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (628 lines) | stat: -rw-r--r-- 22,321 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/display/screen.h"

#import <AppKit/AppKit.h>
#import <ApplicationServices/ApplicationServices.h>
#include <Foundation/Foundation.h>
#include <QuartzCore/CVDisplayLink.h>
#include <stdint.h>

#include <map>
#include <memory>

#include "base/apple/bridging.h"
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "ui/display/display.h"
#include "ui/display/display_change_notifier.h"
#include "ui/display/mac/screen_mac_headless.h"
#include "ui/display/util/display_util.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/mac/coordinate_conversion.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/switches.h"

extern "C" {
Boolean CGDisplayUsesForceToGray(void);
}

namespace display {
namespace {

struct DisplayMac {
  const Display display;
  NSScreen* const __weak ns_screen;
};

NSScreen* GetMatchingScreen(const gfx::Rect& match_rect) {
  // Default to the monitor with the current keyboard focus, in case
  // |match_rect| is not on any screen at all.
  NSScreen* max_screen = NSScreen.mainScreen;
  int max_area = 0;

  for (NSScreen* screen in NSScreen.screens) {
    gfx::Rect monitor_area = gfx::ScreenRectFromNSRect(screen.frame);
    gfx::Rect intersection = gfx::IntersectRects(monitor_area, match_rect);
    int area = intersection.width() * intersection.height();
    if (area > max_area) {
      max_area = area;
      max_screen = screen;
    }
  }

  return max_screen;
}

const std::vector<Display> DisplaysFromDisplaysMac(
    const std::vector<DisplayMac>& displays_mac) {
  std::vector<Display> displays;

  for (auto const& display_mac : displays_mac) {
    displays.push_back(display_mac.display);
  }

  return displays;
}

DisplayMac BuildDisplayForScreen(NSScreen* screen) {
  TRACE_EVENT0("ui", "BuildDisplayForScreen");
  NSRect frame = screen.frame;

  CGDirectDisplayID display_id =
      [screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue];

  Display display(display_id, gfx::Rect(NSRectToCGRect(frame)));
  NSRect visible_frame = screen.visibleFrame;
  NSScreen* primary = NSScreen.screens.firstObject;

  // Convert work area's coordinate systems.
  if ([screen isEqual:primary]) {
    gfx::Rect work_area = gfx::Rect(NSRectToCGRect(visible_frame));
    work_area.set_y(frame.size.height - visible_frame.origin.y -
                    visible_frame.size.height);
    display.set_work_area(work_area);
  } else {
    display.set_bounds(gfx::ScreenRectFromNSRect(frame));
    display.set_work_area(gfx::ScreenRectFromNSRect(visible_frame));
  }

  // Compute device scale factor
  CGFloat scale = screen.backingScaleFactor;
  if (Display::HasForceDeviceScaleFactor())
    scale = Display::GetForcedDeviceScaleFactor();
  display.set_device_scale_factor(scale);

  // Examine the presence of HDR.
  bool enable_hdr = false;
  float hdr_max_lum_relative = 1.f;
  const float max_potential_edr_value =
      screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
  const float max_edr_value =
      screen.maximumExtendedDynamicRangeColorComponentValue;
  if (max_potential_edr_value > 1.f) {
    enable_hdr = true;
#if defined(ARCH_CPU_X86_64)
    // Disable HDR on Intel laptop screens because performance is unacceptably
    // bad.
    // https://crbug.com/1402882
    if (CGDisplayIsBuiltin(display_id) && max_potential_edr_value <= 2.f) {
      enable_hdr = false;
    }
#endif
    if (enable_hdr) {
      hdr_max_lum_relative =
          std::max(kMinHDRCapableMaxLuminanceRelative, max_edr_value);
    }
  }

  // Compute DisplayColorSpaces.
  gfx::ICCProfile icc_profile;
  {
    CGColorSpaceRef cg_color_space = screen.colorSpace.CGColorSpace;
    if (cg_color_space) {
      base::apple::ScopedCFTypeRef<CFDataRef> cf_icc_profile(
          CGColorSpaceCopyICCData(cg_color_space));
      if (cf_icc_profile) {
        icc_profile =
            gfx::ICCProfile::FromData(CFDataGetBytePtr(cf_icc_profile.get()),
                                      CFDataGetLength(cf_icc_profile.get()));
      }
    }
  }
  gfx::DisplayColorSpaces display_color_spaces(icc_profile.GetColorSpace(),
                                               gfx::BufferFormat::BGRA_8888);
  if (HasForceDisplayColorProfile()) {
    if (Display::HasEnsureForcedColorProfile()) {
      if (display_color_spaces != display.GetColorSpaces()) {
        LOG(FATAL) << "The display's color space does not match the color "
                      "space that was forced by the command line. This will "
                      "cause pixel tests to fail.";
      }
    }
  } else {
    if (enable_hdr) {
      bool needs_alpha_values[] = {true, false};
      for (const auto& needs_alpha : needs_alpha_values) {
        display_color_spaces.SetOutputColorSpaceAndBufferFormat(
            gfx::ContentColorUsage::kHDR, needs_alpha,
            gfx::ColorSpace::CreateExtendedSRGB(), gfx::BufferFormat::RGBA_F16);
      }
      display_color_spaces.SetHDRMaxLuminanceRelative(hdr_max_lum_relative);
    }
    display.SetColorSpaces(display_color_spaces);
  }
  display_color_spaces.SetSDRMaxLuminanceNits(
      gfx::ColorSpace::kDefaultSDRWhiteLevel);

  if (enable_hdr) {
    display.set_color_depth(Display::kHDR10BitsPerPixel);
    display.set_depth_per_component(Display::kHDR10BitsPerComponent);
  } else {
    display.set_color_depth(Display::kDefaultBitsPerPixel);
    display.set_depth_per_component(Display::kDefaultBitsPerComponent);
  }
  display.set_is_monochrome(CGDisplayUsesForceToGray());

  // Query the display's refresh rate.
  if (@available(macos 12.0, *)) {
    // NSScreen.minimumRefreshInterval is available on macOS 12.0+
    double refresh_rate = 1.0 / screen.minimumRefreshInterval;
    display.set_display_frequency(refresh_rate);
  } else {
    // CVDisplayLink is available on macOS 10.4–15.0.
    CVDisplayLinkRef display_link = nullptr;
    if (CVDisplayLinkCreateWithCGDisplay(display_id, &display_link) ==
        kCVReturnSuccess) {
      DCHECK(display_link);
      CVTime cv_time =
          CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
      if (!(cv_time.flags & kCVTimeIsIndefinite)) {
        double refresh_rate = (static_cast<double>(cv_time.timeScale) /
                               static_cast<double>(cv_time.timeValue));
        display.set_display_frequency(refresh_rate);
      }
      CVDisplayLinkRelease(display_link);
    }
  }

  // CGDisplayRotation returns a double. Display::SetRotationAsDegree will
  // handle the unexpected situations were the angle is not a multiple of 90.
  display.SetRotationAsDegree(static_cast<int>(CGDisplayRotation(display_id)));

  // TODO(crbug.com/40129700): Support multiple internal displays.
  // CGDisplayIsBuiltin may return -1 on [dis]connect; see crbug.com/1457025.
  if (CGDisplayIsBuiltin(display_id) == YES) {
    SetInternalDisplayIds({display_id});
  }

  display.set_label(base::SysNSStringToUTF8(screen.localizedName));

  return DisplayMac{display, screen};
}

DisplayMac BuildPrimaryDisplay() {
  return BuildDisplayForScreen(NSScreen.screens.firstObject);
}

std::vector<DisplayMac> BuildDisplaysFromQuartz() {
  TRACE_EVENT0("ui", "BuildDisplaysFromQuartz");

  // Don't just return all online displays.  This would include displays
  // that mirror other displays, which are not desired in this list.  It's
  // tempting to use the count returned by CGGetActiveDisplayList, but active
  // displays exclude sleeping displays, and those are desired.

  // It would be ridiculous to have this many displays connected, but
  // CGDirectDisplayID is just an integer, so supporting up to this many
  // doesn't hurt.
  CGDirectDisplayID online_displays[1024];
  CGDisplayCount online_display_count = 0;
  if (CGGetOnlineDisplayList(std::size(online_displays), online_displays,
                             &online_display_count) != kCGErrorSuccess) {
    return std::vector<DisplayMac>(1, BuildPrimaryDisplay());
  }

  using ScreenIdsToScreensMap = std::map<CGDirectDisplayID, NSScreen*>;
  ScreenIdsToScreensMap screen_ids_to_screens;
  for (NSScreen* screen in NSScreen.screens) {
    NSDictionary* screen_device_description = [screen deviceDescription];
    CGDirectDisplayID screen_id =
        [screen_device_description[@"NSScreenNumber"] unsignedIntValue];
    screen_ids_to_screens[screen_id] = screen;
  }

  std::vector<DisplayMac> displays_mac;
  for (CGDisplayCount online_display_index = 0;
       online_display_index < online_display_count; ++online_display_index) {
    CGDirectDisplayID online_display = online_displays[online_display_index];
    if (CGDisplayMirrorsDisplay(online_display) == kCGNullDirectDisplay) {
      // If this display doesn't mirror any other, include it in the list.
      // The primary display in a mirrored set will be counted, but those that
      // mirror it will not be.
      auto foundScreen = screen_ids_to_screens.find(online_display);
      if (foundScreen != screen_ids_to_screens.end()) {
        displays_mac.push_back(BuildDisplayForScreen(foundScreen->second));
      }
    }
  }

  return displays_mac.empty()
             ? std::vector<DisplayMac>(1, BuildPrimaryDisplay())
             : displays_mac;
}

// Returns the minimum Manhattan distance from |point| to corners of |screen|
// frame.
CGFloat GetMinimumDistanceToCorner(const NSPoint& point, NSScreen* screen) {
  NSRect frame = [screen frame];
  CGFloat distance =
      fabs(point.x - NSMinX(frame)) + fabs(point.y - NSMinY(frame));
  distance = std::min(
      distance, fabs(point.x - NSMaxX(frame)) + fabs(point.y - NSMinY(frame)));
  distance = std::min(
      distance, fabs(point.x - NSMinX(frame)) + fabs(point.y - NSMaxY(frame)));
  distance = std::min(
      distance, fabs(point.x - NSMaxX(frame)) + fabs(point.y - NSMaxY(frame)));
  return distance;
}

class ScreenMac : public Screen {
 public:
  ScreenMac() {
    UpdateDisplays();

    CGDisplayRegisterReconfigurationCallback(
        ScreenMac::DisplayReconfigurationCallBack, this);

    auto update_block = ^(NSNotification* notification) {
      OnNSScreensMayHaveChanged();
    };

    NSNotificationCenter* center = NSNotificationCenter.defaultCenter;
    screen_color_change_observer_ =
        [center addObserverForName:NSScreenColorSpaceDidChangeNotification
                            object:nil
                             queue:nil
                        usingBlock:update_block];
    screen_params_change_observer_ = [center
        addObserverForName:NSApplicationDidChangeScreenParametersNotification
                    object:nil
                     queue:nil
                usingBlock:update_block];
  }

  ScreenMac(const ScreenMac&) = delete;
  ScreenMac& operator=(const ScreenMac&) = delete;

  ~ScreenMac() override {
    NSNotificationCenter* center = NSNotificationCenter.defaultCenter;
    [center removeObserver:screen_color_change_observer_];
    [center removeObserver:screen_params_change_observer_];

    CGDisplayRemoveReconfigurationCallback(
        ScreenMac::DisplayReconfigurationCallBack, this);
  }

  gfx::Point GetCursorScreenPoint() override {
    // Flip coordinates to gfx (0,0 in top-left corner) using primary screen.
    return gfx::ScreenPointFromNSPoint([NSEvent mouseLocation]);
  }

  bool IsWindowUnderCursor(gfx::NativeWindow native_window) override {
    NSWindow* window = native_window.GetNativeNSWindow();
    return [NSWindow windowNumberAtPoint:NSEvent.mouseLocation
               belowWindowWithWindowNumber:0] == window.windowNumber;
  }

  gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override {
    NOTIMPLEMENTED();
    return gfx::NativeWindow();
  }

  gfx::NativeWindow GetLocalProcessWindowAtPoint(
      const gfx::Point& point,
      const std::set<gfx::NativeWindow>& ignore) override {
    const NSPoint ns_point = gfx::ScreenPointToNSPoint(point);

    // Note: NSApp.orderedWindows doesn't include NSPanels.
    for (NSWindow* window in NSApp.orderedWindows) {
      if (ignore.count(gfx::NativeWindow(window))) {
        continue;
      }

      if (!window.onActiveSpace) {
        continue;
      }

      // NativeWidgetMac::Close() calls -orderOut: on NSWindows before actually
      // closing them.
      if (!window.visible) {
        continue;
      }

      if (NSPointInRect(ns_point, window.frame)) {
        return gfx::NativeWindow(window);
      }
    }

    return gfx::NativeWindow();
  }

  int GetNumDisplays() const override { return displays_mac_.size(); }

  const std::vector<Display>& GetAllDisplays() const override {
    return displays_;
  }

  Display GetDisplayNearestWindow(
      gfx::NativeWindow native_window) const override {
    if (displays_.size() == 1)
      return displays_[0];

    NSWindow* window = native_window.GetNativeNSWindow();
    if (!window)
      return GetPrimaryDisplay();

    // Note the following line calls -[NSWindow
    // _bestScreenBySpaceAssignmentOrGeometry] which is quite expensive and
    // performs IPC with the window server process.
    NSScreen* match_screen = window.screen;

    if (!match_screen)
      return GetPrimaryDisplay();

    return GetCachedDisplayForScreen(match_screen);
  }

  Display GetDisplayNearestView(gfx::NativeView native_view) const override {
    NSView* view = native_view.GetNativeNSView();
    NSWindow* window = view.window;
    if (!window) {
      return GetPrimaryDisplay();
    }
    return GetDisplayNearestWindow(gfx::NativeWindow(window));
  }

  Display GetDisplayNearestPoint(const gfx::Point& point) const override {
    NSArray* screens = NSScreen.screens;
    if (screens.count <= 1) {
      return GetPrimaryDisplay();
    }

    NSPoint ns_point = NSPointFromCGPoint(point.ToCGPoint());
    NSScreen* primary = screens[0];
    ns_point.y = NSMaxY(primary.frame) - ns_point.y;
    for (NSScreen* screen in screens) {
      if (NSMouseInRect(ns_point, screen.frame, NO)) {
        return GetCachedDisplayForScreen(screen);
      }
    }

    NSScreen* nearest_screen = primary;
    CGFloat min_distance = CGFLOAT_MAX;
    for (NSScreen* screen in screens) {
      CGFloat distance = GetMinimumDistanceToCorner(ns_point, screen);
      if (distance < min_distance) {
        min_distance = distance;
        nearest_screen = screen;
      }
    }
    return GetCachedDisplayForScreen(nearest_screen);
  }

  // Returns the display that most closely intersects the provided bounds.
  Display GetDisplayMatching(const gfx::Rect& match_rect) const override {
    NSScreen* match_screen = GetMatchingScreen(match_rect);
    return GetCachedDisplayForScreen(match_screen);
  }

  // Returns the primary display.
  Display GetPrimaryDisplay() const override {
    // Primary display is defined as the display with the menubar,
    // which is always at index 0.
    NSScreen* primary = NSScreen.screens.firstObject;
    Display display = GetCachedDisplayForScreen(primary);
    return display;
  }

  void AddObserver(DisplayObserver* observer) override {
    change_notifier_.AddObserver(observer);
  }

  void RemoveObserver(DisplayObserver* observer) override {
    change_notifier_.RemoveObserver(observer);
  }

  static void DisplayReconfigurationCallBack(CGDirectDisplayID display,
                                             CGDisplayChangeSummaryFlags flags,
                                             void* userInfo) {
    ScreenMac* screen_mac = static_cast<ScreenMac*>(userInfo);
    screen_mac->OnNSScreensMayHaveChanged();
  }

 private:
  // Updates the display data structures.
  void UpdateDisplays() {
    displays_mac_ = BuildDisplaysFromQuartz();

    std::vector<Display> displays = DisplaysFromDisplaysMac(displays_mac_);
    if (displays != displays_) {
      DISPLAY_LOG(EVENT) << "Displays updated, count: " << displays.size();
      for (const auto& display : displays) {
        DISPLAY_LOG(EVENT) << display.ToString();
      }
    }

    // Keep |displays_| in sync with |displays_mac_|. It would be better to have
    // only the |displays_mac_| data structure and generate an array of Displays
    // from it as needed but GetAllDisplays() is defined as returning a
    // reference. There are no restrictions on how long a caller to
    // GetAllDisplays() can hold onto the reference so we have to assume callers
    // expect the vector's contents to always reflect the current state of the
    // world. Therefore update |displays_| whenever we update |displays_mac_|.
    displays_ = std::move(displays);
  }

  Display GetCachedDisplayForScreen(NSScreen* screen) const {
    for (const DisplayMac& display_mac : displays_mac_) {
      if (display_mac.ns_screen == screen)
        return display_mac.display;
    }
    // In theory, this should not be reached, but in practice, on Catalina, it
    // has been observed that -[NSScreen screens] changes before any
    // notifications are received. See crbug.com/1021340 and crbug.com/1352564
    DISPLAY_LOG(DEBUG) << "-[NSScreen screens] changed before notification.";
    return BuildDisplayForScreen(screen).display;
  }

  void OnDelayedNotification() {
    // This can only be called `delayed_notification_new_displays_` is identical
    // to `displays_` except for HDR headroom.
    DCHECK_EQ(delayed_notification_new_displays_.size(), displays_.size());
    for (size_t i = 0; i < displays_.size(); ++i) {
      DCHECK(display::Display::EqualExceptForHdrHeadroom(
          displays_[i], delayed_notification_new_displays_[i]));
    }

    // Update `displays_` and send the notification.
    auto old_displays = std::move(displays_);
    displays_ = std::move(delayed_notification_new_displays_);
    delayed_notification_new_displays_.clear();
    change_notifier_.NotifyDisplaysChanged(old_displays, displays_);
  }

  void OnNSScreensMayHaveChanged() {
    TRACE_EVENT0("ui", "OnNSScreensMayHaveChanged");

    auto old_displays = std::move(displays_);

    UpdateDisplays();

    // Determine if anything changed, and if anything besides HDR headroom
    // changed.
    bool all_displays_equal = true;
    bool all_displays_equal_except_hdr_headroom = true;
    if (displays_.size() != old_displays.size()) {
      all_displays_equal = false;
      all_displays_equal_except_hdr_headroom = false;
    } else {
      for (size_t i = 0; i < displays_.size(); ++i) {
        if (!display::Display::EqualExceptForHdrHeadroom(displays_[i],
                                                         old_displays[i])) {
          all_displays_equal = false;
          all_displays_equal_except_hdr_headroom = false;
          break;
        }
        if (displays_[i] != old_displays[i]) {
          all_displays_equal = false;
        }
      }
    }

    if (NSScreen.screens.firstObject != primary_ns_screen_) {
      primary_ns_screen_ = NSScreen.screens.firstObject;
      change_notifier_.NotifyPrimaryDisplayChanged();
    }

    // If nothing changed, do no notifications.
    if (all_displays_equal) {
      return;
    }

#if defined(ARCH_CPU_X86_64)
    // HDR transitions on Intel can have extremely bad performance, so limit
    // their updates to 2 FPS.
    constexpr auto kMinimumHdrHeadroomUpdateInterval = base::Seconds(1 / 2.f);
#else
    // Allow HDR headroom updates at 12 FPS. Empirically, this is the minimum
    // framerate that doesn't feel janky.
    constexpr auto kMinimumHdrHeadroomUpdateInterval = base::Seconds(1 / 12.f);
#endif

    // If only HDR headroom changed, start a timer to do delayed notifications
    // (only if it has not already started).
    if (all_displays_equal_except_hdr_headroom) {
      delayed_notification_new_displays_ = std::move(displays_);
      displays_ = std::move(old_displays);
      if (!delayed_notification_timer_.IsRunning()) {
        delayed_notification_timer_.Start(
            FROM_HERE, kMinimumHdrHeadroomUpdateInterval,
            base::BindOnce(&ScreenMac::OnDelayedNotification,
                           weak_factory_.GetWeakPtr()));
      }
      return;
    }

    // Stop and delete any delayed notifications, because we're doing an update
    // now.
    delayed_notification_new_displays_.clear();
    delayed_notification_timer_.Stop();

    // Do the update.
    change_notifier_.NotifyDisplaysChanged(old_displays, displays_);
  }

  // The displays currently attached to the device. Updated by
  // OnNSScreensMayHaveChanged.
  std::vector<DisplayMac> displays_mac_;

  std::vector<Display> displays_;

  NSScreen* __weak primary_ns_screen_ = nil;

  // The observers notified by NSScreenColorSpaceDidChangeNotification and
  // NSApplicationDidChangeScreenParametersNotification.
  id __strong screen_color_change_observer_;
  id __strong screen_params_change_observer_;

  DisplayChangeNotifier change_notifier_;

  // If only the HDR headroom changed, throttle display notification changes to
  // avoid choppy performance. Start`delayed_notification_timer_` to call
  // OnDelayedNotification, which will update `displays_` to
  // `delayed_notification_new_displays_`.
  base::OneShotTimer delayed_notification_timer_;
  std::vector<Display> delayed_notification_new_displays_;
  base::WeakPtrFactory<ScreenMac> weak_factory_{this};
};

}  // namespace

// static
gfx::NativeWindow Screen::GetWindowForView(gfx::NativeView native_view) {
  NSView* view = native_view.GetNativeNSView();
  return gfx::NativeWindow(view.window);
}

Screen* CreateNativeScreen() {
  const base::CommandLine& command_line =
      CHECK_DEREF(base::CommandLine::ForCurrentProcess());

  if (command_line.HasSwitch(switches::kHeadless)) {
    return new ScreenMacHeadless;
  }

  return new ScreenMac;
}

}  // namespace display