summarylogtreecommitdiffstats
path: root/0009-ozone-wayland-window-state-change-must-be-synchronou.patch
diff options
context:
space:
mode:
Diffstat (limited to '0009-ozone-wayland-window-state-change-must-be-synchronou.patch')
-rw-r--r--0009-ozone-wayland-window-state-change-must-be-synchronou.patch665
1 files changed, 665 insertions, 0 deletions
diff --git a/0009-ozone-wayland-window-state-change-must-be-synchronou.patch b/0009-ozone-wayland-window-state-change-must-be-synchronou.patch
new file mode 100644
index 000000000000..1386e82e4e62
--- /dev/null
+++ b/0009-ozone-wayland-window-state-change-must-be-synchronou.patch
@@ -0,0 +1,665 @@
+From f70d9e1475b402cc70ff0b4539c26ff86ef1116c Mon Sep 17 00:00:00 2001
+From: Maksim Sisov <msisov@igalia.com>
+Date: Tue, 7 Jan 2020 18:17:05 +0000
+Subject: [PATCH 9/9] ozone/wayland: window state change must be synchronous.
+
+This CL fixes two issues:
+
+1) After we changed to only create ShellSurfaces after Show calls, starting the
+browser with maximized or fullscreen modes broke. That is, in Wayland, it
+is required to assign a role to wl_surface and make higher level objects like
+xdg_surface and xdg_toplevel that are used by desktop xdg shell.
+
+State manipulation can only be done through those higher level objects and
+we must defer setting state to maximized/fullscreen until they are created.
+
+2) Also, this CL changes the intermidiate result of state changes: now, all of
+them are considered synchronous and if Wayland decides to change the state
+to something else, the state will be overriden and OnWindowStateChange will
+be called. Otherwise, if it was the client, who changed the state, no
+notification is sent.
+
+Test: StartMaximized, StartWithFullscreen, CompositorSideChanges
+Bug: 1033525
+
+Change-Id: I89728297d572d62db59f2d3b1628f2d735dbd4b0
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1965031
+Reviewed-by: Michael Spang <spang@chromium.org>
+Commit-Queue: Maksim Sisov <msisov@igalia.com>
+Cr-Commit-Position: refs/heads/master@{#728996}
+---
+ .../platform/wayland/host/wayland_surface.cc | 173 ++++++--------
+ .../platform/wayland/host/wayland_surface.h | 17 +-
+ .../wayland/host/wayland_window_unittest.cc | 212 +++++++++++++++---
+ 3 files changed, 249 insertions(+), 153 deletions(-)
+
+diff --git a/ui/ozone/platform/wayland/host/wayland_surface.cc b/ui/ozone/platform/wayland/host/wayland_surface.cc
+index b800a526e64e..c90e499fc796 100644
+--- a/ui/ozone/platform/wayland/host/wayland_surface.cc
++++ b/ui/ozone/platform/wayland/host/wayland_surface.cc
+@@ -19,8 +19,7 @@ namespace ui {
+ WaylandSurface::WaylandSurface(PlatformWindowDelegate* delegate,
+ WaylandConnection* connection)
+ : WaylandWindow(delegate, connection),
+- state_(PlatformWindowState::kNormal),
+- pending_state_(PlatformWindowState::kUnknown) {
++ state_(PlatformWindowState::kNormal) {
+ // Set a class property key, which allows |this| to be used for interactive
+ // events, e.g. move or resize.
+ SetWmMoveResizeHandler(this, AsWmMoveResizeHandler());
+@@ -47,6 +46,7 @@ bool WaylandSurface::CreateShellSurface() {
+ shell_surface_->SetAppId(app_id_);
+ shell_surface_->SetTitle(window_title_);
+ SetSizeConstraints();
++ TriggerStateChanges();
+ return true;
+ }
+
+@@ -117,7 +117,7 @@ void WaylandSurface::Hide() {
+ bool WaylandSurface::IsVisible() const {
+ // X and Windows return true if the window is minimized. For consistency, do
+ // the same.
+- return !!shell_surface_ || IsMinimized();
++ return !!shell_surface_ || state_ == PlatformWindowState::kMinimized;
+ }
+
+ void WaylandSurface::SetTitle(const base::string16& title) {
+@@ -133,74 +133,36 @@ void WaylandSurface::SetTitle(const base::string16& title) {
+ }
+
+ void WaylandSurface::ToggleFullscreen() {
+- DCHECK(shell_surface_);
+-
+- // There are some cases, when Chromium triggers a fullscreen state change
+- // before the surface is activated. In such cases, Wayland may ignore state
+- // changes and such flags as --kiosk or --start-fullscreen will be ignored.
+- // To overcome this, set a pending state, and once the surface is activated,
+- // trigger the change.
+- if (!is_active_) {
+- DCHECK(!IsFullscreen());
+- pending_state_ = PlatformWindowState::kFullScreen;
+- return;
+- }
+-
+ // TODO(msisov, tonikitoo): add multiscreen support. As the documentation says
+- // if shell_surface_set_fullscreen() is not provided with wl_output, it's up
++ // if xdg_toplevel_set_fullscreen() is not provided with wl_output, it's up
+ // to the compositor to choose which display will be used to map this surface.
+- if (!IsFullscreen()) {
+- // Fullscreen state changes have to be handled manually and then checked
+- // against configuration events, which come from a compositor. The reason
+- // of manually changing the |state_| is that the compositor answers about
+- // state changes asynchronously, which leads to a wrong return value in
+- // DesktopWindowTreeHostPlatform::IsFullscreen, for example, and media
+- // files can never be set to fullscreen.
+- state_ = PlatformWindowState::kFullScreen;
+- shell_surface_->SetFullscreen();
++
++ // We must track the previous state to correctly say our state as long as it
++ // can be the maximized instead of normal one.
++ PlatformWindowState new_state = PlatformWindowState::kUnknown;
++ if (state_ == PlatformWindowState::kFullScreen) {
++ if (previous_state_ == PlatformWindowState::kMaximized)
++ new_state = previous_state_;
++ else
++ new_state = PlatformWindowState::kNormal;
+ } else {
+- // Check the comment above. If it's not handled synchronously, media files
+- // may not leave the fullscreen mode.
+- state_ = PlatformWindowState::kUnknown;
+- shell_surface_->UnSetFullscreen();
++ new_state = PlatformWindowState::kFullScreen;
+ }
+
+- connection()->ScheduleFlush();
++ SetWindowState(new_state);
+ }
+
+ void WaylandSurface::Maximize() {
+- DCHECK(shell_surface_);
+-
+- if (IsFullscreen())
+- ToggleFullscreen();
+-
+- shell_surface_->SetMaximized();
+- connection()->ScheduleFlush();
++ SetWindowState(PlatformWindowState::kMaximized);
+ }
+
+ void WaylandSurface::Minimize() {
+- DCHECK(shell_surface_);
+- DCHECK(!is_minimizing_);
+- // Wayland doesn't explicitly say if a window is minimized. Instead, it
+- // notifies that the window is not activated. But there are many cases, when
+- // the window is not minimized and deactivated. In order to properly record
+- // the minimized state, mark this window as being minimized. And as soon as a
+- // configuration event comes, check if the window has been deactivated and has
+- // |is_minimizing_| set.
+- is_minimizing_ = true;
+- shell_surface_->SetMinimized();
+- connection()->ScheduleFlush();
++ SetWindowState(PlatformWindowState::kMinimized);
+ }
+
+ void WaylandSurface::Restore() {
+ DCHECK(shell_surface_);
+-
+- // Unfullscreen the window if it is fullscreen.
+- if (IsFullscreen())
+- ToggleFullscreen();
+-
+- shell_surface_->UnSetMaximized();
+- connection()->ScheduleFlush();
++ SetWindowState(PlatformWindowState::kNormal);
+ }
+
+ PlatformWindowState WaylandSurface::GetPlatformWindowState() const {
+@@ -223,34 +185,21 @@ void WaylandSurface::HandleSurfaceConfigure(int32_t width,
+ bool is_maximized,
+ bool is_fullscreen,
+ bool is_activated) {
+- // Propagate the window state information to the client.
++ // Store the old state to propagte state changes if Wayland decides to change
++ // the state to something else.
+ PlatformWindowState old_state = state_;
+-
+- // Ensure that manually handled state changes to fullscreen correspond to the
+- // configuration events from a compositor.
+- DCHECK_EQ(is_fullscreen, IsFullscreen());
+-
+- // There are two cases, which must be handled for the minimized state.
+- // The first one is the case, when the surface goes into the minimized state
+- // (check comment in WaylandSurface::Minimize), and the second case is when
+- // the surface still has been minimized, but another configuration event with
+- // !is_activated comes. For this, check if the WaylandSurface has been
+- // minimized before and !is_activated is sent.
+- if ((is_minimizing_ || IsMinimized()) && !is_activated) {
+- is_minimizing_ = false;
++ if (state_ == PlatformWindowState::kMinimized && !is_activated) {
+ state_ = PlatformWindowState::kMinimized;
+ } else if (is_fullscreen) {
+- // To ensure the |delegate()| is notified about state changes to fullscreen,
+- // assume the old_state is UNKNOWN (check comment in ToggleFullscreen).
+- old_state = PlatformWindowState::kUnknown;
+- DCHECK(state_ == PlatformWindowState::kFullScreen);
++ state_ = PlatformWindowState::kFullScreen;
+ } else if (is_maximized) {
+ state_ = PlatformWindowState::kMaximized;
+ } else {
+ state_ = PlatformWindowState::kNormal;
+ }
++
+ const bool state_changed = old_state != state_;
+- const bool is_normal = !IsFullscreen() && !IsMaximized();
++ const bool is_normal = state_ == PlatformWindowState::kNormal;
+
+ // Update state before notifying delegate.
+ const bool did_active_change = is_active_ != is_activated;
+@@ -281,28 +230,17 @@ void WaylandSurface::HandleSurfaceConfigure(int32_t width,
+ 1.0 / buffer_scale()));
+ }
+
+- if (state_changed) {
+- // The |restored_bounds_| are used when the window gets back to normal
+- // state after it went maximized or fullscreen. So we reset these if the
+- // window has just become normal and store the current bounds if it is
+- // either going out of normal state or simply changes the state and we don't
+- // have any meaningful value stored.
+- if (is_normal) {
+- SetRestoredBoundsInPixels({});
+- } else if (old_state == PlatformWindowState::kNormal ||
+- GetRestoredBoundsInPixels().IsEmpty()) {
+- SetRestoredBoundsInPixels(GetBounds());
+- }
++ // Store the restored bounds of current state differs from the normal state.
++ // It can be client or compositor side change from normal to something else.
++ // Thus, we must store previous bounds to restore later.
++ SetOrResetRestoredBounds();
++ ApplyPendingBounds();
+
++ if (state_changed)
+ delegate()->OnWindowStateChanged(state_);
+- }
+-
+- ApplyPendingBounds();
+
+ if (did_active_change)
+ delegate()->OnActivationChanged(is_active_);
+-
+- MaybeTriggerPendingStateChange();
+ }
+
+ void WaylandSurface::OnDragEnter(const gfx::PointF& point,
+@@ -348,24 +286,34 @@ bool WaylandSurface::OnInitialize(PlatformWindowInitProperties properties) {
+ return true;
+ }
+
+-bool WaylandSurface::IsMinimized() const {
+- return state_ == PlatformWindowState::kMinimized;
+-}
++void WaylandSurface::TriggerStateChanges() {
++ if (!shell_surface_)
++ return;
+
+-bool WaylandSurface::IsMaximized() const {
+- return state_ == PlatformWindowState::kMaximized;
+-}
++ if (state_ == PlatformWindowState::kFullScreen)
++ shell_surface_->SetFullscreen();
++ else
++ shell_surface_->UnSetFullscreen();
++
++ // Call UnSetMaximized only if current state is normal. Otherwise, if the
++ // current state is fullscreen and the previous is maximized, calling
++ // UnSetMaximized may result in wrong restored window position that clients
++ // are not allowed to know about.
++ if (state_ == PlatformWindowState::kMaximized)
++ shell_surface_->SetMaximized();
++ else if (state_ == PlatformWindowState::kNormal)
++ shell_surface_->UnSetMaximized();
++
++ if (state_ == PlatformWindowState::kMinimized)
++ shell_surface_->SetMinimized();
+
+-bool WaylandSurface::IsFullscreen() const {
+- return state_ == PlatformWindowState::kFullScreen;
++ connection()->ScheduleFlush();
+ }
+
+-void WaylandSurface::MaybeTriggerPendingStateChange() {
+- if (pending_state_ == PlatformWindowState::kUnknown || !is_active_)
+- return;
+- DCHECK_EQ(pending_state_, PlatformWindowState::kFullScreen);
+- pending_state_ = PlatformWindowState::kUnknown;
+- ToggleFullscreen();
++void WaylandSurface::SetWindowState(PlatformWindowState state) {
++ previous_state_ = state_;
++ state_ = state;
++ TriggerStateChanges();
+ }
+
+ WmMoveResizeHandler* WaylandSurface::AsWmMoveResizeHandler() {
+@@ -381,4 +329,17 @@ void WaylandSurface::SetSizeConstraints() {
+ connection()->ScheduleFlush();
+ }
+
++void WaylandSurface::SetOrResetRestoredBounds() {
++ // The |restored_bounds_| are used when the window gets back to normal
++ // state after it went maximized or fullscreen. So we reset these if the
++ // window has just become normal and store the current bounds if it is
++ // either going out of normal state or simply changes the state and we don't
++ // have any meaningful value stored.
++ if (GetPlatformWindowState() == PlatformWindowState::kNormal) {
++ SetRestoredBoundsInPixels({});
++ } else if (GetRestoredBoundsInPixels().IsEmpty()) {
++ SetRestoredBoundsInPixels(GetBounds());
++ }
++}
++
+ } // namespace ui
+diff --git a/ui/ozone/platform/wayland/host/wayland_surface.h b/ui/ozone/platform/wayland/host/wayland_surface.h
+index 677eaca6ef56..ceda32d24a7c 100644
+--- a/ui/ozone/platform/wayland/host/wayland_surface.h
++++ b/ui/ozone/platform/wayland/host/wayland_surface.h
+@@ -69,11 +69,8 @@ class WaylandSurface : public WaylandWindow,
+ void OnDragSessionClose(uint32_t dnd_action) override;
+ bool OnInitialize(PlatformWindowInitProperties properties) override;
+
+- bool IsMinimized() const;
+- bool IsMaximized() const;
+- bool IsFullscreen() const;
+-
+- void MaybeTriggerPendingStateChange();
++ void TriggerStateChanges();
++ void SetWindowState(PlatformWindowState state);
+
+ // Creates a surface window, which is visible as a main window.
+ bool CreateShellSurface();
+@@ -83,6 +80,8 @@ class WaylandSurface : public WaylandWindow,
+ // Propagates the |min_size_| and |max_size_| to the ShellSurface.
+ void SetSizeConstraints();
+
++ void SetOrResetRestoredBounds();
++
+ // Wrappers around shell surface.
+ std::unique_ptr<ShellSurfaceWrapper> shell_surface_;
+
+@@ -100,14 +99,12 @@ class WaylandSurface : public WaylandWindow,
+ // handler that receives DIP from Wayland.
+ gfx::Rect pending_bounds_dip_;
+
+- // Stores current states of the window.
++ // Contains the current state of the window.
+ PlatformWindowState state_;
+- // Stores a pending state of the window, which is used before the surface is
+- // activated.
+- PlatformWindowState pending_state_;
++ // Contains the previous state of the window.
++ PlatformWindowState previous_state_;
+
+ bool is_active_ = false;
+- bool is_minimizing_ = false;
+
+ // Id of the chromium app passed through
+ // PlatformWindowInitProperties::wm_class_class. This is used by Wayland
+diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+index f56b96690eea..c72d27edff66 100644
+--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
++++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+@@ -233,8 +233,7 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) {
+ kMaximizedBounds.height()));
+ EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
+ EXPECT_CALL(delegate_, OnBoundsChanged(kMaximizedBounds));
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kMaximized)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->Maximize();
+ SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 1,
+ active_maximized.get());
+@@ -262,8 +261,7 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) {
+
+ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, kNormalBounds.width(),
+ kNormalBounds.height()));
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kNormal)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
+ EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds));
+ EXPECT_CALL(*GetXdgToplevel(), UnsetMaximized());
+@@ -283,19 +281,21 @@ TEST_P(WaylandWindowTest, Minimize) {
+ Sync();
+
+ EXPECT_CALL(*GetXdgToplevel(), SetMinimized());
+- // Wayland compositor doesn't notify clients about minimized state, but rather
+- // if a window is not activated. Thus, a WaylandWindow marks itself as being
+- // minimized and as soon as a configuration event with not activated state
+- // comes, its state is changed to minimized. This EXPECT_CALL ensures this
+- // behaviour.
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kMinimized)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->Minimize();
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
++
+ // Reinitialize wl_array, which removes previous old states.
+ states = ScopedWlArray();
+ SendConfigureEvent(0, 0, 2, states.get());
+ Sync();
+
++ // Wayland compositor doesn't notify clients about minimized state, but rather
++ // if a window is not activated. Thus, a WaylandSurface marks itself as being
++ // minimized and and sets state to minimized. Thus, the state mustn't change
++ // after the configuration event is sent.
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
++
+ // Send one additional empty configuration event (which means the surface is
+ // not maximized, fullscreen or activated) to ensure, WaylandWindow stays in
+ // the same minimized state and doesn't notify its delegate.
+@@ -319,8 +319,7 @@ TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
+ AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
+
+ EXPECT_CALL(*GetXdgToplevel(), SetFullscreen());
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->ToggleFullscreen();
+ // Make sure than WaylandWindow manually handles fullscreen states. Check the
+ // comment in the WaylandWindow::ToggleFullscreen.
+@@ -330,51 +329,181 @@ TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
+ Sync();
+
+ EXPECT_CALL(*GetXdgToplevel(), UnsetFullscreen());
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kNormal)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->Restore();
+- EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kUnknown);
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
+ // Reinitialize wl_array, which removes previous old states.
+ states = InitializeWlArrayWithActivatedState();
+ SendConfigureEvent(0, 0, 3, states.get());
+ Sync();
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
+ }
+
+ TEST_P(WaylandWindowTest, StartWithFullscreen) {
++ MockPlatformWindowDelegate delegate;
++ PlatformWindowInitProperties properties;
++ properties.bounds = gfx::Rect(0, 0, 100, 100);
++ properties.type = PlatformWindowType::kWindow;
++ // We need to create a window avoid calling Show() on it as it is what upper
++ // views layer does - when Widget initialize DesktopWindowTreeHost, the Show()
++ // is called later down the road, but Maximize may be called earlier. We
++ // cannot process them and set a pending state instead, because ShellSurface
++ // is not created by that moment.
++ auto window = WaylandWindow::Create(&delegate, connection_.get(),
++ std::move(properties));
++
++ Sync();
++
++ // Make sure the window is initialized to normal state from the beginning.
++ EXPECT_EQ(PlatformWindowState::kNormal, window->GetPlatformWindowState());
++
++ // The state must not be changed to the fullscreen before the surface is
++ // activated.
++ auto* mock_surface = server_.GetObject<wl::MockSurface>(window->GetWidget());
++ EXPECT_FALSE(mock_surface->xdg_surface());
++ EXPECT_CALL(delegate, OnWindowStateChanged(_)).Times(0);
++ window->ToggleFullscreen();
++ // The state of the window must already be fullscreen one.
++ EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
++
++ Sync();
++
++ // We mustn't receive any state changes if that does not differ from the last
++ // state.
++ EXPECT_CALL(delegate, OnWindowStateChanged(_)).Times(0);
++
++ // Activate the surface.
++ ScopedWlArray states = InitializeWlArrayWithActivatedState();
++ AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
++ SendConfigureEvent(0, 0, 1, states.get());
++
++ Sync();
++
++ // It must be still the same state.
++ EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
++}
++
++TEST_P(WaylandWindowTest, StartMaximized) {
++ MockPlatformWindowDelegate delegate;
++ PlatformWindowInitProperties properties;
++ properties.bounds = gfx::Rect(0, 0, 100, 100);
++ properties.type = PlatformWindowType::kWindow;
++ // We need to create a window avoid calling Show() on it as it is what upper
++ // views layer does - when Widget initialize DesktopWindowTreeHost, the Show()
++ // is called later down the road, but Maximize may be called earlier. We
++ // cannot process them and set a pending state instead, because ShellSurface
++ // is not created by that moment.
++ auto window = WaylandWindow::Create(&delegate, connection_.get(),
++ std::move(properties));
++
++ Sync();
++
+ // Make sure the window is initialized to normal state from the beginning.
+ EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
+
+ // The state must not be changed to the fullscreen before the surface is
+ // activated.
+- EXPECT_CALL(*GetXdgToplevel(), SetFullscreen()).Times(0);
++ auto* mock_surface = server_.GetObject<wl::MockSurface>(window->GetWidget());
++ EXPECT_FALSE(mock_surface->xdg_surface());
+ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+- window_->ToggleFullscreen();
+- // The state of the window must still be a normal one.
+- EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
++
++ window_->Maximize();
++ // The state of the window must already be fullscreen one.
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
+
+ Sync();
+
+- // Once the surface will be activated, the window will automatically trigger
+- // the state change.
+- EXPECT_CALL(*GetXdgToplevel(), SetFullscreen());
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen)));
++ // Once the surface will be activated, the window state mustn't be changed
++ // and retain the same.
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
+
+ // Activate the surface.
+ ScopedWlArray states = InitializeWlArrayWithActivatedState();
++ AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
+ SendConfigureEvent(0, 0, 1, states.get());
+
+ Sync();
+
+- // The wayland window manually handles the fullscreen state changes, and it
+- // must change to a fullscreen before the state change is confirmed by the
+- // wayland. See comment in the WaylandWindow::ToggleFullscreen.
+- EXPECT_EQ(window_->GetPlatformWindowState(),
+- PlatformWindowState::kFullScreen);
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
++}
+
+- AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
++TEST_P(WaylandWindowTest, CompositorSideStateChanges) {
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
++ auto normal_bounds = window_->GetBounds();
++
++ ScopedWlArray states = InitializeWlArrayWithActivatedState();
++ AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
++ SendConfigureEvent(2000, 2000, 1, states.get());
++
++ EXPECT_CALL(delegate_,
++ OnWindowStateChanged(Eq(PlatformWindowState::kMaximized)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 2000, 2000));
++
++ Sync();
++
++ EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
++
++ // Unmaximize
++ states = InitializeWlArrayWithActivatedState();
+ SendConfigureEvent(0, 0, 2, states.get());
+
++ EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kNormal)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, normal_bounds.width(),
++ normal_bounds.height()));
++
++ // Now, set to fullscreen.
++ AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
++ SendConfigureEvent(2005, 2005, 3, states.get());
++ EXPECT_CALL(delegate_,
++ OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 2005, 2005));
++
++ Sync();
++
++ // Unfullscreen
++ states = InitializeWlArrayWithActivatedState();
++ SendConfigureEvent(0, 0, 4, states.get());
++
++ EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kNormal)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, normal_bounds.width(),
++ normal_bounds.height()));
++
++ Sync();
++
++ // Now, maximize, fullscreen and restore.
++ states = InitializeWlArrayWithActivatedState();
++ AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
++ SendConfigureEvent(2000, 2000, 1, states.get());
++
++ EXPECT_CALL(delegate_,
++ OnWindowStateChanged(Eq(PlatformWindowState::kMaximized)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 2000, 2000));
++
++ Sync();
++
++ AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
++ SendConfigureEvent(2005, 2005, 1, states.get());
++
++ EXPECT_CALL(delegate_,
++ OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 2005, 2005));
++
++ // Restore
++ states = InitializeWlArrayWithActivatedState();
++ SendConfigureEvent(0, 0, 4, states.get());
++
++ EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kNormal)))
++ .Times(1);
++ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, normal_bounds.width(),
++ normal_bounds.height()));
++
+ Sync();
+ }
+
+@@ -395,25 +524,33 @@ TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) {
+ kMaximizedBounds.height()));
+ EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
+ EXPECT_CALL(delegate_, OnBoundsChanged(kMaximizedBounds));
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kMaximized)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->Maximize();
++ // State changes are synchronous.
++ EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
+ SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 2,
+ active_maximized.get());
+ Sync();
++ // Verify that the state has not been changed.
++ EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
+ VerifyAndClearExpectations();
+
+ EXPECT_CALL(*GetXdgToplevel(), SetFullscreen());
+ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, kMaximizedBounds.width(),
+ kMaximizedBounds.height()));
+ EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->ToggleFullscreen();
++ // State changes are synchronous.
++ EXPECT_EQ(PlatformWindowState::kFullScreen,
++ window_->GetPlatformWindowState());
+ AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, active_maximized.get());
+ SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 3,
+ active_maximized.get());
+ Sync();
++ // Verify that the state has not been changed.
++ EXPECT_EQ(PlatformWindowState::kFullScreen,
++ window_->GetPlatformWindowState());
+ VerifyAndClearExpectations();
+
+ EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, kNormalBounds.width(),
+@@ -421,13 +558,14 @@ TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) {
+ EXPECT_CALL(*GetXdgToplevel(), UnsetFullscreen());
+ EXPECT_CALL(*GetXdgToplevel(), UnsetMaximized());
+ EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds));
+- EXPECT_CALL(delegate_,
+- OnWindowStateChanged(Eq(PlatformWindowState::kNormal)));
++ EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0);
+ window_->Restore();
++ EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
+ // Reinitialize wl_array, which removes previous old states.
+ auto active = InitializeWlArrayWithActivatedState();
+ SendConfigureEvent(0, 0, 4, active.get());
+ Sync();
++ EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
+ }
+
+ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximize) {
+--
+2.24.1
+