From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Christopher Cameron <ccameron@chromium.org>
Date: Thu, 7 Nov 2019 18:28:51 +0000
Subject: Fix hi-dpi transitions on Catalina

A few issues here:

First, actually wire up NativeWidgetNSWindowBridge as a DisplayObserver.
In the past, it didn't matter that this code was missing, because we
were getting notified via windowDidChangeBackingProperties. In Catalina,
that call is happening at a different time, resulting us using an
invalid cached version (which is the second issue).

Second, change GetDisplayNearestWindow to call UpdateDisplaysIfNeeded.
There was a bug here wherein we would return displays_[0], even if we
knew (because of displays_require_update_) that that value was out of
date.

Thid, make GetCachedDisplayForScreen be robust to getting a surprise
NSScreen* that we didn't know about. On Catalina, it happens that we
can read -[NSScreen screens] and see that it has changed, before having
received any notifications that such a change would happen (!).

Fourth, listen for NSApplicationDidChangeScreenParametersNotification
notifications. Just because it sounds like a healthy thing to be doing.

Bug: 1021340
Change-Id: Ibe5a6469d9e2c39cd81d0fb19ee2cfe3aedb1488
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1902508
Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: ccameron <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713490}

diff --git a/components/remote_cocoa/app_shim/bridged_native_widget_impl.h b/components/remote_cocoa/app_shim/bridged_native_widget_impl.h
index 9378a4cc6ced0ee4e9c8c3736ec051ca21e581b8..07625bb6729822cb771418307d51039f193eaf69 100644
--- a/components/remote_cocoa/app_shim/bridged_native_widget_impl.h
+++ b/components/remote_cocoa/app_shim/bridged_native_widget_impl.h
@@ -191,6 +191,8 @@ class REMOTE_COCOA_APP_SHIM_EXPORT BridgedNativeWidgetImpl
   void SaveKeyEventForRedispatch(NSEvent* event);
 
   // display::DisplayObserver:
+  void OnDisplayAdded(const display::Display& new_display) override;
+  void OnDisplayRemoved(const display::Display& old_display) override;
   void OnDisplayMetricsChanged(const display::Display& display,
                                uint32_t metrics) override;
 
diff --git a/components/remote_cocoa/app_shim/bridged_native_widget_impl.mm b/components/remote_cocoa/app_shim/bridged_native_widget_impl.mm
index 402b6099126e0ac8dd3b6be5c0ffb1a1946dc139..c6b03fe02d9228bc22b9343687ea845c77c47d8d 100644
--- a/components/remote_cocoa/app_shim/bridged_native_widget_impl.mm
+++ b/components/remote_cocoa/app_shim/bridged_native_widget_impl.mm
@@ -310,9 +310,11 @@ BridgedNativeWidgetImpl::BridgedNativeWidgetImpl(
       bridge_mojo_binding_(this) {
   DCHECK(GetIdToWidgetImplMap().find(id_) == GetIdToWidgetImplMap().end());
   GetIdToWidgetImplMap().insert(std::make_pair(id_, this));
+  display::Screen::GetScreen()->AddObserver(this);
 }
 
 BridgedNativeWidgetImpl::~BridgedNativeWidgetImpl() {
+  display::Screen::GetScreen()->RemoveObserver(this);
   // The delegate should be cleared already. Note this enforces the precondition
   // that -[NSWindow close] is invoked on the hosted window before the
   // destructor is called.
@@ -1102,7 +1104,17 @@ remote_cocoa::DragDropClient* BridgedNativeWidgetImpl::drag_drop_client() {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// BridgedNativeWidgetImpl, ui::CATransactionObserver
+// BridgedNativeWidgetImpl, display::DisplayObserver:
+
+void BridgedNativeWidgetImpl::OnDisplayAdded(
+    const display::Display& display) {
+  UpdateWindowDisplay();
+}
+
+void BridgedNativeWidgetImpl::OnDisplayRemoved(
+    const display::Display& display) {
+  UpdateWindowDisplay();
+}
 
 void BridgedNativeWidgetImpl::OnDisplayMetricsChanged(
     const display::Display& display,
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm
index 463ff7105ac329cafed793fd87cfc8423e0a0ed7..6b5424c3dae77585bc95b2da48d20168706b3f33 100644
--- a/ui/display/mac/screen_mac.mm
+++ b/ui/display/mac/screen_mac.mm
@@ -31,8 +31,8 @@ Boolean CGDisplayUsesForceToGray(void);
 namespace display {
 namespace {
 
-// The delay to handle the display configuration changes.
-// See comments in ScreenMac::HandleDisplayReconfiguration.
+// The delay to handle the display configuration changes. This is in place to
+// coalesce display update notifications and thereby avoid thrashing.
 const int64_t kConfigureDelayMs = 500;
 
 NSScreen* GetMatchingScreen(const gfx::Rect& match_rect) {
@@ -155,20 +155,27 @@ class ScreenMac : public Screen {
     CGDisplayRegisterReconfigurationCallback(
         ScreenMac::DisplayReconfigurationCallBack, this);
 
+    auto update_block = ^(NSNotification* notification) {
+      OnNSScreensMayHaveChanged();
+    };
+
     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
     screen_color_change_observer_.reset(
         [[center addObserverForName:NSScreenColorSpaceDidChangeNotification
                              object:nil
                               queue:nil
-                         usingBlock:^(NSNotification* notification) {
-                           configure_timer_.Reset();
-                           displays_require_update_ = true;
-                         }] retain]);
+                         usingBlock:update_block] retain]);
+    screen_params_change_observer_.reset([[center
+        addObserverForName:NSApplicationDidChangeScreenParametersNotification
+                    object:nil
+                     queue:nil
+                usingBlock:update_block] retain]);
   }
 
   ~ScreenMac() override {
     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
     [center removeObserver:screen_color_change_observer_];
+    [center removeObserver:screen_params_change_observer_];
 
     CGDisplayRemoveReconfigurationCallback(
         ScreenMac::DisplayReconfigurationCallBack, this);
@@ -193,16 +200,18 @@ class ScreenMac : public Screen {
   int GetNumDisplays() const override { return GetAllDisplays().size(); }
 
   const std::vector<Display>& GetAllDisplays() const override {
+    UpdateDisplaysIfNeeded();
     return displays_;
   }
 
   Display GetDisplayNearestWindow(
       gfx::NativeWindow native_window) const override {
-    NSWindow* window = native_window.GetNativeNSWindow();
-    EnsureDisplaysValid();
+    UpdateDisplaysIfNeeded();
+
     if (displays_.size() == 1)
       return displays_[0];
 
+    NSWindow* window = native_window.GetNativeNSWindow();
     if (!window)
       return GetPrimaryDisplay();
 
@@ -275,31 +284,30 @@ class ScreenMac : public Screen {
   static void DisplayReconfigurationCallBack(CGDirectDisplayID display,
                                              CGDisplayChangeSummaryFlags flags,
                                              void* userInfo) {
-    if (flags & kCGDisplayBeginConfigurationFlag)
-      return;
-
     ScreenMac* screen_mac = static_cast<ScreenMac*>(userInfo);
-
-    // Timer::Reset() ensures at least another interval passes before the
-    // associated task runs, effectively coalescing these events.
-    screen_mac->configure_timer_.Reset();
-    screen_mac->displays_require_update_ = true;
+    screen_mac->OnNSScreensMayHaveChanged();
   }
 
  private:
   Display GetCachedDisplayForScreen(NSScreen* screen) const {
-    EnsureDisplaysValid();
+    UpdateDisplaysIfNeeded();
     const CGDirectDisplayID display_id = [[[screen deviceDescription]
         objectForKey:@"NSScreenNumber"] unsignedIntValue];
     for (const Display& display : displays_) {
       if (display_id == display.id())
         return display;
     }
-    NOTREACHED();  // Asked for a hidden/sleeping/mirrored screen?
+    // In theory, this should not be reached, because |displays_require_update_|
+    // should have been set prior to -[NSScreen screens] changing. In practice,
+    // on Catalina, it has been observed that -[NSScreen screens] changes before
+    // any notifications are received.
+    // https://crbug.com/1021340.
+    OnNSScreensMayHaveChanged();
+    DLOG(ERROR) << "Value of -[NSScreen screens] changed before notification.";
     return BuildDisplayForScreen(screen);
   }
 
-  void EnsureDisplaysValid() const {
+  void UpdateDisplaysIfNeeded() const {
     if (displays_require_update_) {
       displays_ = BuildDisplaysFromQuartz();
       displays_require_update_ = false;
@@ -307,7 +315,7 @@ class ScreenMac : public Screen {
   }
 
   void ConfigureTimerFired() {
-    EnsureDisplaysValid();
+    UpdateDisplaysIfNeeded();
     change_notifier_.NotifyDisplaysChanged(old_displays_, displays_);
     old_displays_ = displays_;
   }
@@ -321,7 +329,7 @@ class ScreenMac : public Screen {
     // 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[128];
+    CGDirectDisplayID online_displays[1024];
     CGDisplayCount online_display_count = 0;
     if (CGGetOnlineDisplayList(base::size(online_displays), online_displays,
                                &online_display_count) != kCGErrorSuccess) {
@@ -357,21 +365,32 @@ class ScreenMac : public Screen {
                             : displays;
   }
 
-  // The displays currently attached to the device. Cached.
+  void OnNSScreensMayHaveChanged() const {
+    // Timer::Reset() ensures at least another interval passes before the
+    // associated task runs, effectively coalescing these events.
+    configure_timer_.Reset();
+    displays_require_update_ = true;
+  }
+
+  // The displays currently attached to the device. Updated by
+  // UpdateDisplaysIfNeeded.
   mutable std::vector<Display> displays_;
 
-  // Set whenever the CGDisplayRegisterReconfigurationCallback is invoked and
-  // cleared when |displays_| is updated by BuildDisplaysFromQuartz().
+  // Whether or not |displays_| might need to be upated. Set in
+  // OnNSScreensMayHaveChanged, and un-set by UpdateDisplaysIfNeeded.
   mutable bool displays_require_update_ = false;
 
-  // The displays last communicated to DisplayChangeNotifier.
-  std::vector<Display> old_displays_;
+  // The timer to delay configuring outputs and notifying observers (to coalesce
+  // several updates into one update).
+  mutable base::RetainingOneShotTimer configure_timer_;
 
-  // The timer to delay configuring outputs and notifying observers.
-  base::RetainingOneShotTimer configure_timer_;
+  // The displays last communicated to the DisplayChangeNotifier.
+  std::vector<Display> old_displays_;
 
-  // The observer notified by NSScreenColorSpaceDidChangeNotification.
+  // The observers notified by NSScreenColorSpaceDidChangeNotification and
+  // NSApplicationDidChangeScreenParametersNotification.
   base::scoped_nsobject<id> screen_color_change_observer_;
+  base::scoped_nsobject<id> screen_params_change_observer_;
 
   DisplayChangeNotifier change_notifier_;
 
