1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/exo/client_controlled_shell_surface.h"
6 
7 #include <map>
8 #include <utility>
9 
10 #include "ash/frame/header_view.h"
11 #include "ash/frame/non_client_frame_view_ash.h"
12 #include "ash/frame/wide_frame_view.h"
13 #include "ash/public/cpp/ash_features.h"
14 #include "ash/public/cpp/rounded_corner_decorator.h"
15 #include "ash/public/cpp/shell_window_ids.h"
16 #include "ash/public/cpp/window_backdrop.h"
17 #include "ash/public/cpp/window_properties.h"
18 #include "ash/root_window_controller.h"
19 #include "ash/shell.h"
20 #include "ash/wm/client_controlled_state.h"
21 #include "ash/wm/collision_detection/collision_detection_utils.h"
22 #include "ash/wm/drag_details.h"
23 #include "ash/wm/pip/pip_positioner.h"
24 #include "ash/wm/splitview/split_view_controller.h"
25 #include "ash/wm/toplevel_window_event_handler.h"
26 #include "ash/wm/window_positioning_utils.h"
27 #include "ash/wm/window_properties.h"
28 #include "ash/wm/window_state.h"
29 #include "ash/wm/window_state_delegate.h"
30 #include "ash/wm/window_util.h"
31 #include "base/logging.h"
32 #include "base/no_destructor.h"
33 #include "base/numerics/safe_conversions.h"
34 #include "base/strings/utf_string_conversions.h"
35 #include "base/trace_event/trace_event.h"
36 #include "base/trace_event/traced_value.h"
37 #include "chromeos/ui/base/tablet_state.h"
38 #include "chromeos/ui/base/window_pin_type.h"
39 #include "chromeos/ui/base/window_properties.h"
40 #include "chromeos/ui/base/window_state_type.h"
41 #include "chromeos/ui/frame/caption_buttons/caption_button_model.h"
42 #include "chromeos/ui/frame/default_frame_header.h"
43 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
44 #include "components/exo/shell_surface_util.h"
45 #include "components/exo/surface.h"
46 #include "components/exo/wm_helper.h"
47 #include "ui/aura/client/aura_constants.h"
48 #include "ui/aura/scoped_window_event_targeting_blocker.h"
49 #include "ui/aura/window.h"
50 #include "ui/aura/window_event_dispatcher.h"
51 #include "ui/aura/window_observer.h"
52 #include "ui/aura/window_tree_host.h"
53 #include "ui/base/class_property.h"
54 #include "ui/compositor/compositor_lock.h"
55 #include "ui/display/display.h"
56 #include "ui/display/screen.h"
57 #include "ui/display/tablet_state.h"
58 #include "ui/gfx/geometry/point.h"
59 #include "ui/gfx/geometry/size.h"
60 #include "ui/views/widget/widget.h"
61 #include "ui/wm/core/coordinate_conversion.h"
62 #include "ui/wm/core/window_util.h"
63 
64 namespace exo {
65 
66 namespace {
67 
68 using ::chromeos::WindowStateType;
69 
70 // Client controlled specific accelerators.
71 const struct {
72   ui::KeyboardCode keycode;
73   int modifiers;
74   ClientControlledAcceleratorAction action;
75 } kAccelerators[] = {
76     {ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN,
77      ClientControlledAcceleratorAction::ZOOM_OUT},
78     {ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN,
79      ClientControlledAcceleratorAction::ZOOM_IN},
80     {ui::VKEY_0, ui::EF_CONTROL_DOWN,
81      ClientControlledAcceleratorAction::ZOOM_RESET},
82 };
83 
GetFactoryForTesting()84 ClientControlledShellSurface::DelegateFactoryCallback& GetFactoryForTesting() {
85   using CallbackType = ClientControlledShellSurface::DelegateFactoryCallback;
86   static base::NoDestructor<CallbackType> factory;
87   return *factory;
88 }
89 
90 // Maximum amount of time to wait for contents that match the display's
91 // orientation in tablet mode.
92 // TODO(oshima): Looks like android is generating unnecessary frames.
93 // Fix it on Android side and reduce the timeout.
94 constexpr int kOrientationLockTimeoutMs = 2500;
95 
SizeToOrientation(const gfx::Size & size)96 Orientation SizeToOrientation(const gfx::Size& size) {
97   DCHECK_NE(size.width(), size.height());
98   return size.width() > size.height() ? Orientation::LANDSCAPE
99                                       : Orientation::PORTRAIT;
100 }
101 
102 // A ClientControlledStateDelegate that sends the state/bounds
103 // change request to exo client.
104 class ClientControlledStateDelegate
105     : public ash::ClientControlledState::Delegate {
106  public:
ClientControlledStateDelegate(ClientControlledShellSurface * shell_surface)107   explicit ClientControlledStateDelegate(
108       ClientControlledShellSurface* shell_surface)
109       : shell_surface_(shell_surface) {}
~ClientControlledStateDelegate()110   ~ClientControlledStateDelegate() override {}
111 
112   // Overridden from ash::ClientControlledState::Delegate:
HandleWindowStateRequest(ash::WindowState * window_state,chromeos::WindowStateType next_state)113   void HandleWindowStateRequest(ash::WindowState* window_state,
114                                 chromeos::WindowStateType next_state) override {
115     shell_surface_->OnWindowStateChangeEvent(window_state->GetStateType(),
116                                              next_state);
117   }
HandleBoundsRequest(ash::WindowState * window_state,chromeos::WindowStateType requested_state,const gfx::Rect & bounds_in_display,int64_t display_id)118   void HandleBoundsRequest(ash::WindowState* window_state,
119                            chromeos::WindowStateType requested_state,
120                            const gfx::Rect& bounds_in_display,
121                            int64_t display_id) override {
122     shell_surface_->OnBoundsChangeEvent(
123         window_state->GetStateType(), requested_state, display_id,
124         bounds_in_display,
125         window_state->drag_details() && shell_surface_->IsDragging()
126             ? window_state->drag_details()->bounds_change
127             : 0);
128   }
129 
130  private:
131   ClientControlledShellSurface* shell_surface_;
132 
133   DISALLOW_COPY_AND_ASSIGN(ClientControlledStateDelegate);
134 };
135 
136 // A WindowStateDelegate that implements ToggleFullscreen behavior for
137 // client controlled window.
138 class ClientControlledWindowStateDelegate : public ash::WindowStateDelegate {
139  public:
ClientControlledWindowStateDelegate(ClientControlledShellSurface * shell_surface,ash::ClientControlledState::Delegate * delegate)140   explicit ClientControlledWindowStateDelegate(
141       ClientControlledShellSurface* shell_surface,
142       ash::ClientControlledState::Delegate* delegate)
143       : shell_surface_(shell_surface), delegate_(delegate) {}
~ClientControlledWindowStateDelegate()144   ~ClientControlledWindowStateDelegate() override {}
145 
146   // Overridden from ash::WindowStateDelegate:
ToggleFullscreen(ash::WindowState * window_state)147   bool ToggleFullscreen(ash::WindowState* window_state) override {
148     chromeos::WindowStateType next_state;
149     aura::Window* window = window_state->window();
150     switch (window_state->GetStateType()) {
151       case chromeos::WindowStateType::kDefault:
152       case chromeos::WindowStateType::kNormal:
153         window->SetProperty(aura::client::kPreFullscreenShowStateKey,
154                             ui::SHOW_STATE_NORMAL);
155         next_state = chromeos::WindowStateType::kFullscreen;
156         break;
157       case chromeos::WindowStateType::kMaximized:
158         window->SetProperty(aura::client::kPreFullscreenShowStateKey,
159                             ui::SHOW_STATE_MAXIMIZED);
160         next_state = chromeos::WindowStateType::kFullscreen;
161         break;
162       case chromeos::WindowStateType::kFullscreen:
163         switch (window->GetProperty(aura::client::kPreFullscreenShowStateKey)) {
164           case ui::SHOW_STATE_DEFAULT:
165           case ui::SHOW_STATE_NORMAL:
166             next_state = chromeos::WindowStateType::kNormal;
167             break;
168           case ui::SHOW_STATE_MAXIMIZED:
169             next_state = chromeos::WindowStateType::kMaximized;
170             break;
171           case ui::SHOW_STATE_MINIMIZED:
172             next_state = chromeos::WindowStateType::kMinimized;
173             break;
174           case ui::SHOW_STATE_FULLSCREEN:
175           case ui::SHOW_STATE_INACTIVE:
176           case ui::SHOW_STATE_END:
177             NOTREACHED() << " unknown state :"
178                          << window->GetProperty(
179                                 aura::client::kPreFullscreenShowStateKey);
180             return false;
181         }
182         break;
183       case chromeos::WindowStateType::kMinimized: {
184         ui::WindowShowState pre_full_state =
185             window->GetProperty(aura::client::kPreMinimizedShowStateKey);
186         if (pre_full_state != ui::SHOW_STATE_FULLSCREEN) {
187           window->SetProperty(aura::client::kPreFullscreenShowStateKey,
188                               pre_full_state);
189         }
190         next_state = chromeos::WindowStateType::kFullscreen;
191         break;
192       }
193       default:
194         // TODO(oshima|xdai): Handle SNAP state.
195         return false;
196     }
197     delegate_->HandleWindowStateRequest(window_state, next_state);
198     return true;
199   }
200 
OnDragStarted(int component)201   void OnDragStarted(int component) override {
202     shell_surface_->OnDragStarted(component);
203   }
204 
OnDragFinished(bool canceled,const gfx::PointF & location)205   void OnDragFinished(bool canceled, const gfx::PointF& location) override {
206     shell_surface_->OnDragFinished(canceled, location);
207   }
208 
209  private:
210   ClientControlledShellSurface* shell_surface_;
211   ash::ClientControlledState::Delegate* delegate_;
212 
213   DISALLOW_COPY_AND_ASSIGN(ClientControlledWindowStateDelegate);
214 };
215 
IsPinned(const ash::WindowState * window_state)216 bool IsPinned(const ash::WindowState* window_state) {
217   return window_state->IsPinned() || window_state->IsTrustedPinned();
218 }
219 
220 class CaptionButtonModel : public chromeos::CaptionButtonModel {
221  public:
CaptionButtonModel(uint32_t visible_button_mask,uint32_t enabled_button_mask)222   CaptionButtonModel(uint32_t visible_button_mask, uint32_t enabled_button_mask)
223       : visible_button_mask_(visible_button_mask),
224         enabled_button_mask_(enabled_button_mask) {}
225 
226   // Overridden from ash::CaptionButtonModel:
IsVisible(views::CaptionButtonIcon icon) const227   bool IsVisible(views::CaptionButtonIcon icon) const override {
228     return visible_button_mask_ & (1 << icon);
229   }
IsEnabled(views::CaptionButtonIcon icon) const230   bool IsEnabled(views::CaptionButtonIcon icon) const override {
231     return enabled_button_mask_ & (1 << icon);
232   }
InZoomMode() const233   bool InZoomMode() const override {
234     return visible_button_mask_ & (1 << views::CAPTION_BUTTON_ICON_ZOOM);
235   }
236 
237  private:
238   uint32_t visible_button_mask_;
239   uint32_t enabled_button_mask_;
240 
241   DISALLOW_COPY_AND_ASSIGN(CaptionButtonModel);
242 };
243 
244 // EventTargetingBlocker blocks the event targeting by setting NONE targeting
245 // policy to the window subtrees. It resets to the original policy upon
246 // deletion.
247 class EventTargetingBlocker : aura::WindowObserver {
248  public:
249   EventTargetingBlocker() = default;
250 
~EventTargetingBlocker()251   ~EventTargetingBlocker() override {
252     if (window_)
253       Unregister(window_);
254   }
255 
Block(aura::Window * window)256   void Block(aura::Window* window) {
257     window_ = window;
258     Register(window);
259   }
260 
261  private:
Register(aura::Window * window)262   void Register(aura::Window* window) {
263     window->AddObserver(this);
264     event_targeting_blocker_map_[window] =
265         std::make_unique<aura::ScopedWindowEventTargetingBlocker>(window);
266     for (auto* child : window->children())
267       Register(child);
268   }
269 
Unregister(aura::Window * window)270   void Unregister(aura::Window* window) {
271     window->RemoveObserver(this);
272     event_targeting_blocker_map_.erase(window);
273     for (auto* child : window->children())
274       Unregister(child);
275   }
276 
OnWindowDestroying(aura::Window * window)277   void OnWindowDestroying(aura::Window* window) override {
278     Unregister(window);
279     if (window_ == window)
280       window_ = nullptr;
281   }
282 
283   std::map<aura::Window*,
284            std::unique_ptr<aura::ScopedWindowEventTargetingBlocker>>
285       event_targeting_blocker_map_;
286   aura::Window* window_ = nullptr;
287 
288   DISALLOW_COPY_AND_ASSIGN(EventTargetingBlocker);
289 };
290 
291 }  // namespace
292 
293 class ClientControlledShellSurface::ScopedSetBoundsLocally {
294  public:
ScopedSetBoundsLocally(ClientControlledShellSurface * shell_surface)295   explicit ScopedSetBoundsLocally(ClientControlledShellSurface* shell_surface)
296       : state_(shell_surface->client_controlled_state_) {
297     state_->set_bounds_locally(true);
298   }
~ScopedSetBoundsLocally()299   ~ScopedSetBoundsLocally() { state_->set_bounds_locally(false); }
300 
301  private:
302   ash::ClientControlledState* const state_;
303 
304   DISALLOW_COPY_AND_ASSIGN(ScopedSetBoundsLocally);
305 };
306 
307 class ClientControlledShellSurface::ScopedLockedToRoot {
308  public:
ScopedLockedToRoot(views::Widget * widget)309   explicit ScopedLockedToRoot(views::Widget* widget)
310       : window_(widget->GetNativeWindow()) {
311     window_->SetProperty(ash::kLockedToRootKey, true);
312   }
~ScopedLockedToRoot()313   ~ScopedLockedToRoot() { window_->ClearProperty(ash::kLockedToRootKey); }
314 
315  private:
316   aura::Window* const window_;
317 
318   DISALLOW_COPY_AND_ASSIGN(ScopedLockedToRoot);
319 };
320 
321 ////////////////////////////////////////////////////////////////////////////////
322 // ClientControlledShellSurface, public:
323 
ClientControlledShellSurface(Surface * surface,bool can_minimize,int container,bool default_scale_cancellation)324 ClientControlledShellSurface::ClientControlledShellSurface(
325     Surface* surface,
326     bool can_minimize,
327     int container,
328     bool default_scale_cancellation)
329     : ShellSurfaceBase(surface, gfx::Point(), true, can_minimize, container),
330       current_pin_(chromeos::WindowPinType::kNone),
331       use_default_scale_cancellation_(default_scale_cancellation) {}
332 
~ClientControlledShellSurface()333 ClientControlledShellSurface::~ClientControlledShellSurface() {
334   // Reset the window delegate here so that we won't try to do any dragging
335   // operation on a to-be-destroyed window. |widget_| can be nullptr in tests.
336   if (GetWidget())
337     GetWindowState()->SetDelegate(nullptr);
338   if (client_controlled_state_)
339     client_controlled_state_->ResetDelegate();
340   wide_frame_.reset();
341   if (current_pin_ != chromeos::WindowPinType::kNone)
342     SetPinned(chromeos::WindowPinType::kNone);
343 }
344 
SetBounds(int64_t display_id,const gfx::Rect & bounds)345 void ClientControlledShellSurface::SetBounds(int64_t display_id,
346                                              const gfx::Rect& bounds) {
347   TRACE_EVENT2("exo", "ClientControlledShellSurface::SetBounds", "display_id",
348                display_id, "bounds", bounds.ToString());
349 
350   if (bounds.IsEmpty()) {
351     DLOG(WARNING) << "Bounds must be non-empty";
352     return;
353   }
354 
355   SetDisplay(display_id);
356   EnsurePendingScale();
357 
358   const gfx::Rect bounds_dp =
359       gfx::ScaleToRoundedRect(bounds, GetClientToDpPendingScale());
360   SetGeometry(bounds_dp);
361 }
362 
SetBoundsOrigin(const gfx::Point & origin)363 void ClientControlledShellSurface::SetBoundsOrigin(const gfx::Point& origin) {
364   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetBoundsOrigin", "origin",
365                origin.ToString());
366 
367   EnsurePendingScale();
368   const gfx::Point origin_dp =
369       gfx::ScaleToRoundedPoint(origin, GetClientToDpPendingScale());
370   pending_geometry_.set_origin(origin_dp);
371 }
372 
SetBoundsSize(const gfx::Size & size)373 void ClientControlledShellSurface::SetBoundsSize(const gfx::Size& size) {
374   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetBoundsSize", "size",
375                size.ToString());
376 
377   if (size.IsEmpty()) {
378     DLOG(WARNING) << "Bounds size must be non-empty";
379     return;
380   }
381 
382   EnsurePendingScale();
383   const gfx::Size size_dp =
384       gfx::ScaleToRoundedSize(size, GetClientToDpPendingScale());
385   pending_geometry_.set_size(size_dp);
386 }
387 
SetMaximized()388 void ClientControlledShellSurface::SetMaximized() {
389   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetMaximized");
390   pending_window_state_ = chromeos::WindowStateType::kMaximized;
391 }
392 
SetMinimized()393 void ClientControlledShellSurface::SetMinimized() {
394   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetMinimized");
395   pending_window_state_ = chromeos::WindowStateType::kMinimized;
396 }
397 
SetRestored()398 void ClientControlledShellSurface::SetRestored() {
399   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetRestored");
400   pending_window_state_ = chromeos::WindowStateType::kNormal;
401 }
402 
SetFullscreen(bool fullscreen)403 void ClientControlledShellSurface::SetFullscreen(bool fullscreen) {
404   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetFullscreen",
405                "fullscreen", fullscreen);
406   pending_window_state_ = fullscreen ? chromeos::WindowStateType::kFullscreen
407                                      : chromeos::WindowStateType::kNormal;
408 }
409 
SetSnappedToLeft()410 void ClientControlledShellSurface::SetSnappedToLeft() {
411   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetSnappedToLeft");
412   pending_window_state_ = chromeos::WindowStateType::kLeftSnapped;
413 }
414 
SetSnappedToRight()415 void ClientControlledShellSurface::SetSnappedToRight() {
416   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetSnappedToRight");
417   pending_window_state_ = chromeos::WindowStateType::kRightSnapped;
418 }
419 
SetPip()420 void ClientControlledShellSurface::SetPip() {
421   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetPip");
422   pending_window_state_ = chromeos::WindowStateType::kPip;
423 }
424 
SetPinned(chromeos::WindowPinType type)425 void ClientControlledShellSurface::SetPinned(chromeos::WindowPinType type) {
426   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetPinned", "type",
427                static_cast<int>(type));
428 
429   if (!widget_)
430     CreateShellSurfaceWidget(ui::SHOW_STATE_NORMAL);
431 
432   widget_->GetNativeWindow()->SetProperty(chromeos::kWindowPinTypeKey, type);
433   current_pin_ = type;
434 }
435 
SetSystemUiVisibility(bool autohide)436 void ClientControlledShellSurface::SetSystemUiVisibility(bool autohide) {
437   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetSystemUiVisibility",
438                "autohide", autohide);
439 
440   if (!widget_)
441     CreateShellSurfaceWidget(ui::SHOW_STATE_NORMAL);
442 
443   ash::window_util::SetAutoHideShelf(widget_->GetNativeWindow(), autohide);
444 }
445 
SetAlwaysOnTop(bool always_on_top)446 void ClientControlledShellSurface::SetAlwaysOnTop(bool always_on_top) {
447   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetAlwaysOnTop",
448                "always_on_top", always_on_top);
449   pending_always_on_top_ = always_on_top;
450 }
451 
SetImeBlocked(bool ime_blocked)452 void ClientControlledShellSurface::SetImeBlocked(bool ime_blocked) {
453   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetImeBlocked",
454                "ime_blocked", ime_blocked);
455 
456   if (!widget_)
457     CreateShellSurfaceWidget(ui::SHOW_STATE_NORMAL);
458 
459   WMHelper::GetInstance()->SetImeBlocked(widget_->GetNativeWindow(),
460                                          ime_blocked);
461 }
462 
SetOrientation(Orientation orientation)463 void ClientControlledShellSurface::SetOrientation(Orientation orientation) {
464   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetOrientation",
465                "orientation",
466                orientation == Orientation::PORTRAIT ? "portrait" : "landscape");
467   pending_orientation_ = orientation;
468 }
469 
SetShadowBounds(const gfx::Rect & bounds)470 void ClientControlledShellSurface::SetShadowBounds(const gfx::Rect& bounds) {
471   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetShadowBounds", "bounds",
472                bounds.ToString());
473   auto shadow_bounds =
474       bounds.IsEmpty() ? base::nullopt : base::make_optional(bounds);
475   if (shadow_bounds_ != shadow_bounds) {
476     shadow_bounds_ = shadow_bounds;
477     shadow_bounds_changed_ = true;
478   }
479 }
480 
SetScale(double scale)481 void ClientControlledShellSurface::SetScale(double scale) {
482   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetScale", "scale", scale);
483 
484   if (scale <= 0.0) {
485     DLOG(WARNING) << "Surface scale must be greater than 0";
486     return;
487   }
488 
489   pending_scale_ = scale;
490 }
491 
CommitPendingScale()492 void ClientControlledShellSurface::CommitPendingScale() {
493   if (pending_scale_ == scale_ || pending_scale_ == 0.0)
494     return;
495 
496   gfx::Transform transform;
497   transform.Scale(1.0 / pending_scale_, 1.0 / pending_scale_);
498   host_window()->SetTransform(transform);
499   scale_ = pending_scale_;
500 }
501 
SetTopInset(int height)502 void ClientControlledShellSurface::SetTopInset(int height) {
503   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetTopInset", "height",
504                height);
505 
506   pending_top_inset_height_ = height;
507 }
508 
SetResizeOutset(int outset)509 void ClientControlledShellSurface::SetResizeOutset(int outset) {
510   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetResizeOutset", "outset",
511                outset);
512   // Deprecated.
513   NOTREACHED();
514 }
515 
OnWindowStateChangeEvent(chromeos::WindowStateType current_state,chromeos::WindowStateType next_state)516 void ClientControlledShellSurface::OnWindowStateChangeEvent(
517     chromeos::WindowStateType current_state,
518     chromeos::WindowStateType next_state) {
519   // Android already knows this state change. Don't send state change to Android
520   // that it is about to do anyway.
521   if (state_changed_callback_ && pending_window_state_ != next_state)
522     state_changed_callback_.Run(current_state, next_state);
523 }
524 
StartDrag(int component,const gfx::PointF & location)525 void ClientControlledShellSurface::StartDrag(int component,
526                                              const gfx::PointF& location) {
527   TRACE_EVENT2("exo", "ClientControlledShellSurface::StartDrag", "component",
528                component, "location", location.ToString());
529 
530   if (!widget_)
531     return;
532   AttemptToStartDrag(component, location);
533 }
534 
AttemptToStartDrag(int component,const gfx::PointF & location)535 void ClientControlledShellSurface::AttemptToStartDrag(
536     int component,
537     const gfx::PointF& location) {
538   aura::Window* target = widget_->GetNativeWindow();
539   ash::ToplevelWindowEventHandler* toplevel_handler =
540       ash::Shell::Get()->toplevel_window_event_handler();
541   aura::Window* mouse_pressed_handler =
542       target->GetHost()->dispatcher()->mouse_pressed_handler();
543   // Start dragging only if ...
544   // 1) touch guesture is in progres.
545   // 2) mouse was pressed on the target or its subsurfaces.
546   if (toplevel_handler->gesture_target() ||
547       (mouse_pressed_handler && target->Contains(mouse_pressed_handler))) {
548     gfx::PointF point_in_root(location);
549     wm::ConvertPointFromScreen(target->GetRootWindow(), &point_in_root);
550     toplevel_handler->AttemptToStartDrag(
551         target, point_in_root, component,
552         ash::ToplevelWindowEventHandler::EndClosure());
553   }
554 }
555 
IsDragging()556 bool ClientControlledShellSurface::IsDragging() {
557   return in_drag_;
558 }
559 
SetCanMaximize(bool can_maximize)560 void ClientControlledShellSurface::SetCanMaximize(bool can_maximize) {
561   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetCanMaximize",
562                "can_maximzie", can_maximize);
563   can_maximize_ = can_maximize;
564   if (widget_)
565     widget_->OnSizeConstraintsChanged();
566 }
567 
UpdateAutoHideFrame()568 void ClientControlledShellSurface::UpdateAutoHideFrame() {
569   if (immersive_fullscreen_controller_) {
570     bool enabled = (frame_type_ == SurfaceFrameType::AUTOHIDE &&
571                     (GetWindowState()->IsMaximizedOrFullscreenOrPinned() ||
572                      GetWindowState()->IsSnapped()));
573     chromeos::ImmersiveFullscreenController::EnableForWidget(widget_, enabled);
574   }
575 }
576 
SetFrameButtons(uint32_t visible_button_mask,uint32_t enabled_button_mask)577 void ClientControlledShellSurface::SetFrameButtons(
578     uint32_t visible_button_mask,
579     uint32_t enabled_button_mask) {
580   if (frame_visible_button_mask_ == visible_button_mask &&
581       frame_enabled_button_mask_ == enabled_button_mask) {
582     return;
583   }
584   frame_visible_button_mask_ = visible_button_mask;
585   frame_enabled_button_mask_ = enabled_button_mask;
586 
587   if (widget_)
588     UpdateCaptionButtonModel();
589 }
590 
SetExtraTitle(const base::string16 & extra_title)591 void ClientControlledShellSurface::SetExtraTitle(
592     const base::string16& extra_title) {
593   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetExtraTitle",
594                "extra_title", base::UTF16ToUTF8(extra_title));
595 
596   if (!widget_) {
597     initial_extra_title_ = extra_title;
598     return;
599   }
600 
601   GetFrameView()->GetHeaderView()->GetFrameHeader()->SetFrameTextOverride(
602       extra_title);
603   if (wide_frame_) {
604     wide_frame_->header_view()->GetFrameHeader()->SetFrameTextOverride(
605         extra_title);
606   }
607 }
608 
SetOrientationLock(ash::OrientationLockType orientation_lock)609 void ClientControlledShellSurface::SetOrientationLock(
610     ash::OrientationLockType orientation_lock) {
611   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetOrientationLock",
612                "orientation_lock", static_cast<int>(orientation_lock));
613 
614   if (!widget_) {
615     initial_orientation_lock_ = orientation_lock;
616     return;
617   }
618 
619   ash::Shell* shell = ash::Shell::Get();
620   shell->screen_orientation_controller()->LockOrientationForWindow(
621       widget_->GetNativeWindow(), orientation_lock);
622 }
623 
SetClientAccessibilityId(int32_t accessibility_id)624 void ClientControlledShellSurface::SetClientAccessibilityId(
625     int32_t accessibility_id) {
626   if (accessibility_id >= 0)
627     client_accessibility_id_ = accessibility_id;
628   else
629     client_accessibility_id_.reset();
630 
631   if (widget_ && widget_->GetNativeWindow()) {
632     SetShellClientAccessibilityId(widget_->GetNativeWindow(),
633                                   client_accessibility_id_);
634   }
635 }
636 
DidReceiveCompositorFrameAck()637 void ClientControlledShellSurface::DidReceiveCompositorFrameAck() {
638   orientation_ = pending_orientation_;
639   // Unlock the compositor after the frame is received by viz so that
640   // screenshot contain the correct frame.
641   if (expected_orientation_ == orientation_)
642     orientation_compositor_lock_.reset();
643   SurfaceTreeHost::DidReceiveCompositorFrameAck();
644 }
645 
OnBoundsChangeEvent(chromeos::WindowStateType current_state,chromeos::WindowStateType requested_state,int64_t display_id,const gfx::Rect & window_bounds,int bounds_change)646 void ClientControlledShellSurface::OnBoundsChangeEvent(
647     chromeos::WindowStateType current_state,
648     chromeos::WindowStateType requested_state,
649     int64_t display_id,
650     const gfx::Rect& window_bounds,
651     int bounds_change) {
652   if (ignore_bounds_change_request_)
653     return;
654 
655   // 1) Do no update the bounds unless we have geometry from client.
656   // 2) Do not update the bounds if window is minimized unless it
657   // exiting the minimzied state.
658   // The bounds will be provided by client when unminimized.
659   if (geometry().IsEmpty() || window_bounds.IsEmpty() ||
660       (widget_->IsMinimized() &&
661        requested_state == chromeos::WindowStateType::kMinimized) ||
662       !bounds_changed_callback_) {
663     return;
664   }
665 
666   // Sends the client bounds, which matches the geometry
667   // when frame is enabled.
668   const gfx::Rect client_bounds = GetClientBoundsForWindowBoundsAndWindowState(
669       window_bounds, requested_state);
670 
671   gfx::Size current_size = GetFrameView()->GetBoundsForClientView().size();
672   bool is_resize = client_bounds.size() != current_size &&
673                    !widget_->IsMaximized() && !widget_->IsFullscreen();
674 
675   const float scale = 1.f / GetClientToDpScale();
676   const gfx::Rect scaled_client_bounds =
677       gfx::ScaleToRoundedRect(client_bounds, scale);
678   bounds_changed_callback_.Run(current_state, requested_state, display_id,
679                                scaled_client_bounds, is_resize, bounds_change);
680 
681   auto* window_state = GetWindowState();
682   if (server_reparent_window_ &&
683       window_state->GetDisplay().id() != display_id) {
684     ScopedSetBoundsLocally scoped_set_bounds(this);
685     int container_id = window_state->window()->parent()->id();
686     aura::Window* new_parent =
687         ash::Shell::GetRootWindowControllerWithDisplayId(display_id)
688             ->GetContainer(container_id);
689     new_parent->AddChild(window_state->window());
690   }
691 }
692 
ChangeZoomLevel(ZoomChange change)693 void ClientControlledShellSurface::ChangeZoomLevel(ZoomChange change) {
694   if (change_zoom_level_callback_)
695     change_zoom_level_callback_.Run(change);
696 }
697 
OnDragStarted(int component)698 void ClientControlledShellSurface::OnDragStarted(int component) {
699   in_drag_ = true;
700   if (drag_started_callback_)
701     drag_started_callback_.Run(component);
702 }
703 
OnDragFinished(bool canceled,const gfx::PointF & location)704 void ClientControlledShellSurface::OnDragFinished(bool canceled,
705                                                   const gfx::PointF& location) {
706   in_drag_ = false;
707   if (!drag_finished_callback_)
708     return;
709 
710   const float scale = 1.f / GetClientToDpScale();
711   const gfx::PointF scaled = gfx::ScalePoint(location, scale);
712   drag_finished_callback_.Run(scaled.x(), scaled.y(), canceled);
713 }
714 
GetClientToDpScale() const715 float ClientControlledShellSurface::GetClientToDpScale() const {
716   // If the default_device_scale_factor is used for scale cancellation,
717   // we expect the client will already send bounds in DP.
718   if (use_default_scale_cancellation_)
719     return 1.f;
720   return 1.f / scale_;
721 }
722 
723 ////////////////////////////////////////////////////////////////////////////////
724 // SurfaceDelegate overrides:
725 
IsInputEnabled(Surface * surface) const726 bool ClientControlledShellSurface::IsInputEnabled(Surface* surface) const {
727   // Client-driven dragging/resizing relies on implicit grab, which ensures that
728   // mouse/touch events are delivered to the focused surface until release, even
729   // if they fall outside surface bounds. However, if the client destroys the
730   // surface with implicit grab, the drag/resize is prematurely ended. Prevent
731   // this by delivering all input events to the root surface, which shares the
732   // lifetime of the shell surface.
733   // TODO(domlaskowski): Remove once the client is provided with an API to hook
734   // into server-driven dragging/resizing.
735   return surface == root_surface();
736 }
737 
OnSetFrame(SurfaceFrameType type)738 void ClientControlledShellSurface::OnSetFrame(SurfaceFrameType type) {
739   pending_frame_type_ = type;
740 }
741 
OnSetFrameColors(SkColor active_color,SkColor inactive_color)742 void ClientControlledShellSurface::OnSetFrameColors(SkColor active_color,
743                                                     SkColor inactive_color) {
744   ShellSurfaceBase::OnSetFrameColors(active_color, inactive_color);
745   if (wide_frame_) {
746     aura::Window* window = wide_frame_->GetWidget()->GetNativeWindow();
747     window->SetProperty(chromeos::kFrameActiveColorKey, active_color);
748     window->SetProperty(chromeos::kFrameInactiveColorKey, inactive_color);
749   }
750 }
751 
752 ////////////////////////////////////////////////////////////////////////////////
753 // aura::WindowObserver overrides:
754 
OnWindowAddedToRootWindow(aura::Window * window)755 void ClientControlledShellSurface::OnWindowAddedToRootWindow(
756     aura::Window* window) {
757   // Window dragging across display moves the window to target display when
758   // dropped, but the actual window bounds comes later from android.  Update the
759   // window bounds now so that the window stays where it is expected to be. (it
760   // may still move if the android sends different bounds).
761   if (client_controlled_state_->set_bounds_locally() ||
762       !GetWindowState()->is_dragged()) {
763     return;
764   }
765 
766   ScopedLockedToRoot scoped_locked_to_root(widget_);
767   UpdateWidgetBounds();
768 }
769 
770 ////////////////////////////////////////////////////////////////////////////////
771 // views::WidgetDelegate overrides:
772 
CanMaximize() const773 bool ClientControlledShellSurface::CanMaximize() const {
774   return can_maximize_;
775 }
776 
777 std::unique_ptr<views::NonClientFrameView>
CreateNonClientFrameView(views::Widget * widget)778 ClientControlledShellSurface::CreateNonClientFrameView(views::Widget* widget) {
779   ash::WindowState* window_state = GetWindowState();
780   std::unique_ptr<ash::ClientControlledState::Delegate> delegate =
781       GetFactoryForTesting()
782           ? GetFactoryForTesting().Run()
783           : std::make_unique<ClientControlledStateDelegate>(this);
784 
785   auto window_delegate = std::make_unique<ClientControlledWindowStateDelegate>(
786       this, delegate.get());
787   auto state =
788       std::make_unique<ash::ClientControlledState>(std::move(delegate));
789   client_controlled_state_ = state.get();
790   window_state->SetStateObject(std::move(state));
791   window_state->SetDelegate(std::move(window_delegate));
792   auto frame_view =
793       CreateNonClientFrameViewInternal(widget, /*client_controlled=*/true);
794   immersive_fullscreen_controller_ =
795       std::make_unique<chromeos::ImmersiveFullscreenController>();
796   static_cast<ash::NonClientFrameViewAsh*>(frame_view.get())
797       ->InitImmersiveFullscreenControllerForView(
798           immersive_fullscreen_controller_.get());
799   return frame_view;
800 }
801 
SaveWindowPlacement(const gfx::Rect & bounds,ui::WindowShowState show_state)802 void ClientControlledShellSurface::SaveWindowPlacement(
803     const gfx::Rect& bounds,
804     ui::WindowShowState show_state) {}
805 
GetSavedWindowPlacement(const views::Widget * widget,gfx::Rect * bounds,ui::WindowShowState * show_state) const806 bool ClientControlledShellSurface::GetSavedWindowPlacement(
807     const views::Widget* widget,
808     gfx::Rect* bounds,
809     ui::WindowShowState* show_state) const {
810   return false;
811 }
812 
813 ////////////////////////////////////////////////////////////////////////////////
814 // views::View overrides:
815 
GetMaximumSize() const816 gfx::Size ClientControlledShellSurface::GetMaximumSize() const {
817   if (can_maximize_) {
818     // On ChromeOS, a window with non empty maximum size is non-maximizable,
819     // even if CanMaximize() returns true. ClientControlledShellSurface
820     // sololy depends on |can_maximize_| to determine if it is maximizable,
821     // so just return empty size.
822     return gfx::Size();
823   } else {
824     return ShellSurfaceBase::GetMaximumSize();
825   }
826 }
827 
OnDeviceScaleFactorChanged(float old_dsf,float new_dsf)828 void ClientControlledShellSurface::OnDeviceScaleFactorChanged(float old_dsf,
829                                                               float new_dsf) {
830   if (!use_default_scale_cancellation_) {
831     SetScale(new_dsf);
832     // Commit scale changes immediately if we expect that the window will not be
833     // resized.
834     if (widget_->IsMaximized() || widget_->IsFullscreen() ||
835         WMHelper::GetInstance()->InTabletMode())
836       CommitPendingScale();
837   }
838 
839   views::View::OnDeviceScaleFactorChanged(old_dsf, new_dsf);
840 
841   UpdateFrameWidth();
842 }
843 
844 ////////////////////////////////////////////////////////////////////////////////
845 // display::DisplayObserver overrides:
846 
OnDisplayMetricsChanged(const display::Display & new_display,uint32_t changed_metrics)847 void ClientControlledShellSurface::OnDisplayMetricsChanged(
848     const display::Display& new_display,
849     uint32_t changed_metrics) {
850   SurfaceTreeHost::OnDisplayMetricsChanged(new_display, changed_metrics);
851 
852   if (!widget_)
853     return;
854 
855   // The PIP window bounds is adjusted in Ash when the screen is rotated, but
856   // Android has an obsolete bounds for a while and applies it incorrectly.
857   // We need to ignore those bounds change until the states are completely
858   // synced on both sides.
859   if (GetWindowState()->IsPip() &&
860       changed_metrics & display::DisplayObserver::DISPLAY_METRIC_ROTATION) {
861     gfx::Rect bounds_after_rotation =
862         ash::PipPositioner::GetSnapFractionAppliedBounds(GetWindowState());
863     display_rotating_with_pip_ =
864         bounds_after_rotation !=
865         GetWindowState()->window()->GetBoundsInScreen();
866   }
867 
868   const display::Screen* screen = display::Screen::GetScreen();
869   display::Display current_display =
870       screen->GetDisplayNearestWindow(widget_->GetNativeWindow());
871   if (current_display.id() != new_display.id())
872     return;
873 
874   bool in_tablet_mode = WMHelper::GetInstance()->InTabletMode();
875 
876   if (!use_default_scale_cancellation_ &&
877       changed_metrics &
878           display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR) {
879     SetScale(new_display.device_scale_factor());
880     // Commit scale changes immediately if we expect that the window will not be
881     // resized.
882     if (widget_->IsMaximized() || widget_->IsFullscreen() || in_tablet_mode)
883       CommitPendingScale();
884   }
885 
886   if (!in_tablet_mode || !widget_->IsActive() ||
887       !(changed_metrics & display::DisplayObserver::DISPLAY_METRIC_ROTATION)) {
888     return;
889   }
890 
891   Orientation target_orientation = SizeToOrientation(new_display.size());
892   if (orientation_ == target_orientation)
893     return;
894   expected_orientation_ = target_orientation;
895   EnsureCompositorIsLockedForOrientationChange();
896 }
897 
898 ////////////////////////////////////////////////////////////////////////////////
899 // ui::CompositorLockClient overrides:
900 
CompositorLockTimedOut()901 void ClientControlledShellSurface::CompositorLockTimedOut() {
902   orientation_compositor_lock_.reset();
903 }
904 
905 ////////////////////////////////////////////////////////////////////////////////
906 // ShellSurfaceBase overrides:
907 
SetWidgetBounds(const gfx::Rect & bounds)908 void ClientControlledShellSurface::SetWidgetBounds(const gfx::Rect& bounds) {
909   const auto* screen = display::Screen::GetScreen();
910   aura::Window* window = widget_->GetNativeWindow();
911   display::Display current_display = screen->GetDisplayNearestWindow(window);
912 
913   bool is_display_move_pending = false;
914   display::Display target_display = current_display;
915 
916   display::Display display;
917   if (screen->GetDisplayWithDisplayId(display_id_, &display)) {
918     bool is_display_stale = display_id_ != current_display.id();
919 
920     // Preserve widget bounds until client acknowledges display move.
921     if (preserve_widget_bounds_ && is_display_stale)
922       return;
923 
924     // True if the window has just been reparented to another root window, and
925     // the move was initiated by the server.
926     // TODO(oshima): Improve the window moving logic. https://crbug.com/875047
927     is_display_move_pending =
928         window->GetProperty(ash::kLockedToRootKey) && is_display_stale;
929 
930     if (!is_display_move_pending)
931       target_display = display;
932 
933     preserve_widget_bounds_ = is_display_move_pending;
934   } else {
935     preserve_widget_bounds_ = false;
936   }
937 
938   if (!use_default_scale_cancellation_) {
939     bool needs_initial_commit = pending_scale_ == 0.0;
940     SetScale(current_display.device_scale_factor());
941     if (needs_initial_commit)
942       CommitPendingScale();
943   }
944 
945   // Calculate a minimum window visibility required bounds.
946   gfx::Rect adjusted_bounds = bounds;
947   if (!is_display_move_pending) {
948     ash::ClientControlledState::AdjustBoundsForMinimumWindowVisibility(
949         target_display.work_area(), &adjusted_bounds);
950     // Collision detection to the bounds set by Android should be applied only
951     // to initial bounds. Do not adjust new bounds as it can be obsolete or in
952     // transit during animation, which results in incorrect resting postiion.
953     // The resting position should be fully controlled by chrome afterwards
954     // because Android isn't aware of Chrome OS System UI.
955     if (GetWindowState()->IsPip() &&
956         !ash::PipPositioner::HasSnapFraction(GetWindowState())) {
957       adjusted_bounds = ash::CollisionDetectionUtils::GetRestingPosition(
958           target_display, adjusted_bounds,
959           ash::CollisionDetectionUtils::RelativePriority::kPictureInPicture);
960     }
961   }
962 
963   if (adjusted_bounds == widget_->GetWindowBoundsInScreen() &&
964       target_display.id() == current_display.id()) {
965     return;
966   }
967 
968   bool set_bounds_locally =
969       display_rotating_with_pip_ ||
970       (GetWindowState()->is_dragged() && !is_display_move_pending);
971 
972   if (set_bounds_locally || client_controlled_state_->set_bounds_locally()) {
973     // Convert from screen to display coordinates.
974     gfx::Point origin = bounds.origin();
975     wm::ConvertPointFromScreen(window->parent(), &origin);
976 
977     // Move the window relative to the current display.
978     {
979       ScopedSetBoundsLocally scoped_set_bounds(this);
980       window->SetBounds(gfx::Rect(origin, adjusted_bounds.size()));
981     }
982     UpdateSurfaceBounds();
983     return;
984   }
985 
986   {
987     ScopedSetBoundsLocally scoped_set_bounds(this);
988     window->SetBoundsInScreen(adjusted_bounds, target_display);
989   }
990 
991   if (bounds != adjusted_bounds || is_display_move_pending) {
992     // Notify client that bounds were adjusted or window moved across displays.
993     auto state_type = GetWindowState()->GetStateType();
994     gfx::Rect adjusted_bounds_in_display(adjusted_bounds);
995 
996     adjusted_bounds_in_display.Offset(
997         -target_display.bounds().OffsetFromOrigin());
998 
999     OnBoundsChangeEvent(state_type, state_type, target_display.id(),
1000                         adjusted_bounds_in_display, 0);
1001   }
1002 
1003   UpdateSurfaceBounds();
1004 }
1005 
GetShadowBounds() const1006 gfx::Rect ClientControlledShellSurface::GetShadowBounds() const {
1007   gfx::Rect shadow_bounds = ShellSurfaceBase::GetShadowBounds();
1008   const ash::NonClientFrameViewAsh* frame_view = GetFrameView();
1009   if (frame_view->GetVisible()) {
1010     // The client controlled geometry is only for the client
1011     // area. When the chrome side frame is enabled, the shadow height
1012     // has to include the height of the frame, and the total height is
1013     // equals to the window height computed by
1014     // |GetWindowBoundsForClientBounds|.
1015     shadow_bounds.set_size(
1016         frame_view->GetWindowBoundsForClientBounds(shadow_bounds).size());
1017   }
1018 
1019   return shadow_bounds;
1020 }
1021 
InitializeWindowState(ash::WindowState * window_state)1022 void ClientControlledShellSurface::InitializeWindowState(
1023     ash::WindowState* window_state) {
1024   // Set the relevant window properties for Arc apps.
1025   SetArcAppType(window_state->window());
1026 
1027   // Allow the client to request bounds that do not fill the entire work area
1028   // when maximized, or the entire display when fullscreen.
1029   window_state->set_allow_set_bounds_direct(true);
1030   window_state->set_ignore_keyboard_bounds_change(true);
1031   if (container_ == ash::kShellWindowId_SystemModalContainer ||
1032       container_ == ash::kShellWindowId_ArcVirtualKeyboardContainer) {
1033     DisableMovement();
1034   }
1035   ash::NonClientFrameViewAsh* frame_view = GetFrameView();
1036   frame_view->SetCaptionButtonModel(std::make_unique<CaptionButtonModel>(
1037       frame_visible_button_mask_, frame_enabled_button_mask_));
1038   UpdateAutoHideFrame();
1039   UpdateFrameWidth();
1040   if (initial_orientation_lock_ != ash::OrientationLockType::kAny)
1041     SetOrientationLock(initial_orientation_lock_);
1042   if (initial_extra_title_ != base::string16())
1043     SetExtraTitle(initial_extra_title_);
1044 
1045   // Register Client controlled accelerators.
1046   views::FocusManager* focus_manager = widget_->GetFocusManager();
1047   accelerator_target_ =
1048       std::make_unique<ClientControlledAcceleratorTarget>(this);
1049 
1050   // These shortcuts are same as ones used in chrome.
1051   // TODO: investigate if we need to reassign.
1052   for (const auto& entry : kAccelerators) {
1053     focus_manager->RegisterAccelerator(
1054         ui::Accelerator(entry.keycode, entry.modifiers),
1055         ui::AcceleratorManager::kNormalPriority, accelerator_target_.get());
1056     accelerator_target_->RegisterAccelerator(
1057         ui::Accelerator(entry.keycode, entry.modifiers), entry.action);
1058   }
1059 
1060   auto* window = widget_->GetNativeWindow();
1061   SetShellClientAccessibilityId(window, client_accessibility_id_);
1062 }
1063 
GetScale() const1064 float ClientControlledShellSurface::GetScale() const {
1065   return scale_;
1066 }
1067 
GetWidgetBounds() const1068 base::Optional<gfx::Rect> ClientControlledShellSurface::GetWidgetBounds()
1069     const {
1070   const ash::NonClientFrameViewAsh* frame_view = GetFrameView();
1071   if (frame_view->GetVisible()) {
1072     gfx::Rect visible_bounds = ShellSurfaceBase::GetVisibleBounds();
1073     if (widget_->IsMaximized() && frame_type_ == SurfaceFrameType::NORMAL) {
1074       // When the widget is maximized in clamshell mode, client sends
1075       // |geometry_| without taking caption height into account.
1076       visible_bounds.Offset(0, frame_view->NonClientTopBorderHeight());
1077     }
1078     return frame_view->GetWindowBoundsForClientBounds(visible_bounds);
1079   }
1080 
1081   return GetVisibleBounds();
1082 }
1083 
GetSurfaceOrigin() const1084 gfx::Point ClientControlledShellSurface::GetSurfaceOrigin() const {
1085   return gfx::Point();
1086 }
1087 
OnPreWidgetCommit()1088 bool ClientControlledShellSurface::OnPreWidgetCommit() {
1089   if (!widget_) {
1090     // Modify the |origin_| to the |pending_geometry_| to place the window on
1091     // the intended display. See b/77472684 for details.
1092     // TODO(domlaskowski): Remove this once clients migrate to geometry API with
1093     // explicit target display.
1094     if (!pending_geometry_.IsEmpty())
1095       origin_ = pending_geometry_.origin();
1096     CreateShellSurfaceWidget(
1097         chromeos::ToWindowShowState(pending_window_state_));
1098   }
1099 
1100   // Finish ignoring obsolete bounds update as the state changes caused by
1101   // display rotation are synced.
1102   // TODO(takise): This assumes no other bounds update happens during screen
1103   // rotation. Implement more robust logic to handle synchronization for
1104   // screen rotation.
1105   if (pending_geometry_ != geometry_)
1106     display_rotating_with_pip_ = false;
1107 
1108   ash::WindowState* window_state = GetWindowState();
1109   state_changed_ = window_state->GetStateType() != pending_window_state_;
1110   if (!state_changed_) {
1111     // Animate PIP window movement unless it is being dragged.
1112     client_controlled_state_->set_next_bounds_change_animation_type(
1113         window_state->IsPip() && !window_state->is_dragged()
1114             ? ash::ClientControlledState::kAnimationAnimated
1115             : ash::ClientControlledState::kAnimationNone);
1116     return true;
1117   }
1118 
1119   if (IsPinned(window_state)) {
1120     VLOG(1) << "State change was requested while pinned";
1121     return true;
1122   }
1123 
1124   auto animation_type = ash::ClientControlledState::kAnimationNone;
1125   switch (pending_window_state_) {
1126     case chromeos::WindowStateType::kNormal:
1127       if (widget_->IsMaximized() || widget_->IsFullscreen()) {
1128         animation_type = ash::ClientControlledState::kAnimationCrossFade;
1129       }
1130       break;
1131 
1132     case chromeos::WindowStateType::kMaximized:
1133     case chromeos::WindowStateType::kFullscreen:
1134       if (!window_state->IsPip())
1135         animation_type = ash::ClientControlledState::kAnimationCrossFade;
1136       break;
1137 
1138     default:
1139       break;
1140   }
1141 
1142   if (pending_window_state_ == chromeos::WindowStateType::kPip) {
1143     if (ash::features::IsPipRoundedCornersEnabled()) {
1144       decorator_ = std::make_unique<ash::RoundedCornerDecorator>(
1145           window_state->window(), host_window(), host_window()->layer(),
1146           ash::kPipRoundedCornerRadius);
1147     }
1148   } else {
1149     decorator_.reset();  // Remove rounded corners.
1150   }
1151 
1152   bool wasPip = window_state->IsPip();
1153 
1154   // As the bounds of the widget is updated later, ensure that no bounds change
1155   // happens with this state change (e.g. updatePipBounds can be triggered).
1156   base::AutoReset<bool> resetter(&ignore_bounds_change_request_, true);
1157   if (client_controlled_state_->EnterNextState(window_state,
1158                                                pending_window_state_)) {
1159     client_controlled_state_->set_next_bounds_change_animation_type(
1160         animation_type);
1161   }
1162 
1163   if (wasPip && !window_state->IsMinimized()) {
1164     // Expanding PIP should end tablet split view (see crbug.com/941788).
1165     // Clamshell split view does not require special handling. We activate the
1166     // PIP window, and so overview ends, which means clamshell split view ends.
1167     // TODO(edcourtney): Consider not ending tablet split view on PIP expand.
1168     // See crbug.com/950827.
1169     ash::SplitViewController* split_view_controller =
1170         ash::SplitViewController::Get(ash::Shell::GetPrimaryRootWindow());
1171     if (split_view_controller->InTabletSplitViewMode())
1172       split_view_controller->EndSplitView();
1173     // As Android doesn't activate PIP tasks after they are expanded, we need
1174     // to do it here explicitly.
1175     // TODO(937738): Investigate if we can activate PIP windows inside commit.
1176     window_state->Activate();
1177   }
1178 
1179   return true;
1180 }
1181 
OnPostWidgetCommit()1182 void ClientControlledShellSurface::OnPostWidgetCommit() {
1183   DCHECK(widget_);
1184 
1185   UpdateFrame();
1186   UpdateBackdrop();
1187 
1188   if (geometry_changed_callback_) {
1189     // Since the visible bounds are in screen coordinates, do not scale these
1190     // bounds with the display's scale before sending them.
1191     // TODO(b/167286795): Instead of sending bounds in screen coordinates, send
1192     // the bounds in the display along with the display information, similar to
1193     // the bounds_changed_callback_.
1194     geometry_changed_callback_.Run(GetVisibleBounds());
1195   }
1196 
1197   // Apply new top inset height.
1198   if (pending_top_inset_height_ != top_inset_height_) {
1199     widget_->GetNativeWindow()->SetProperty(aura::client::kTopViewInset,
1200                                             pending_top_inset_height_);
1201     top_inset_height_ = pending_top_inset_height_;
1202   }
1203 
1204   // Update surface scale.
1205   if (use_default_scale_cancellation_)
1206     CommitPendingScale();
1207 
1208   widget_->GetNativeWindow()->SetProperty(aura::client::kZOrderingKey,
1209                                           pending_always_on_top_
1210                                               ? ui::ZOrderLevel::kFloatingWindow
1211                                               : ui::ZOrderLevel::kNormal);
1212 
1213   ash::WindowState* window_state = GetWindowState();
1214   // For PIP, the snap fraction is used to specify the ideal position. Usually
1215   // this value is set in CompleteDrag, but for the initial position, we need
1216   // to set it here, when the transition is completed.
1217   if (window_state->IsPip() &&
1218       !ash::PipPositioner::HasSnapFraction(window_state)) {
1219     ash::PipPositioner::SaveSnapFraction(
1220         window_state, window_state->window()->GetBoundsInScreen());
1221   }
1222 
1223   ShellSurfaceBase::OnPostWidgetCommit();
1224 }
1225 
OnSurfaceDestroying(Surface * surface)1226 void ClientControlledShellSurface::OnSurfaceDestroying(Surface* surface) {
1227   if (client_controlled_state_)
1228     client_controlled_state_->ResetDelegate();
1229   ShellSurfaceBase::OnSurfaceDestroying(surface);
1230 }
1231 
OnContentSizeChanged(Surface * surface)1232 void ClientControlledShellSurface::OnContentSizeChanged(Surface* surface) {
1233   CommitPendingScale();
1234 }
1235 
1236 ////////////////////////////////////////////////////////////////////////////////
1237 // ClientControlledShellSurface, private:
1238 
UpdateFrame()1239 void ClientControlledShellSurface::UpdateFrame() {
1240   if (!widget_)
1241     return;
1242   gfx::Rect work_area =
1243       display::Screen::GetScreen()
1244           ->GetDisplayNearestWindow(widget_->GetNativeWindow())
1245           .work_area();
1246 
1247   ash::WindowState* window_state = GetWindowState();
1248   bool enable_wide_frame = GetFrameView()->GetVisible() &&
1249                            window_state->IsMaximizedOrFullscreenOrPinned() &&
1250                            work_area.width() != geometry().width();
1251   bool update_frame = state_changed_;
1252   state_changed_ = false;
1253   if (enable_wide_frame) {
1254     if (!wide_frame_) {
1255       update_frame = true;
1256       wide_frame_ = std::make_unique<ash::WideFrameView>(widget_);
1257       chromeos::ImmersiveFullscreenController::EnableForWidget(widget_, false);
1258       wide_frame_->Init(immersive_fullscreen_controller_.get());
1259       wide_frame_->header_view()->GetFrameHeader()->SetFrameTextOverride(
1260           GetFrameView()
1261               ->GetHeaderView()
1262               ->GetFrameHeader()
1263               ->frame_text_override());
1264       wide_frame_->GetWidget()->Show();
1265 
1266       // Restoring window targeter replaced by ImmersiveFullscreenController.
1267       InstallCustomWindowTargeter();
1268 
1269       UpdateCaptionButtonModel();
1270     }
1271     DCHECK_EQ(chromeos::FrameHeader::Get(widget_),
1272               wide_frame_->header_view()->GetFrameHeader());
1273   } else {
1274     if (wide_frame_) {
1275       update_frame = true;
1276       chromeos::ImmersiveFullscreenController::EnableForWidget(widget_, false);
1277       wide_frame_.reset();
1278       GetFrameView()->InitImmersiveFullscreenControllerForView(
1279           immersive_fullscreen_controller_.get());
1280       // Restoring window targeter replaced by ImmersiveFullscreenController.
1281       InstallCustomWindowTargeter();
1282 
1283       UpdateCaptionButtonModel();
1284     }
1285     DCHECK_EQ(chromeos::FrameHeader::Get(widget_),
1286               GetFrameView()->GetHeaderView()->GetFrameHeader());
1287     UpdateFrameWidth();
1288   }
1289   // The autohide should be applied when the window state is in
1290   // maximzied, fullscreen or pinned. Update the auto hide state
1291   // inside commit.
1292   if (update_frame)
1293     UpdateAutoHideFrame();
1294 }
1295 
UpdateCaptionButtonModel()1296 void ClientControlledShellSurface::UpdateCaptionButtonModel() {
1297   auto model = std::make_unique<CaptionButtonModel>(frame_visible_button_mask_,
1298                                                     frame_enabled_button_mask_);
1299   if (wide_frame_)
1300     wide_frame_->SetCaptionButtonModel(std::move(model));
1301   else
1302     GetFrameView()->SetCaptionButtonModel(std::move(model));
1303 }
1304 
UpdateBackdrop()1305 void ClientControlledShellSurface::UpdateBackdrop() {
1306   aura::Window* window = widget_->GetNativeWindow();
1307 
1308   // Always create a backdrop regardless of the geometry because
1309   // maximized/fullscreen widget's geometry can be cropped.
1310   bool enable_backdrop = widget_->IsFullscreen() || widget_->IsMaximized();
1311 
1312   ash::WindowBackdrop::BackdropMode target_backdrop_mode =
1313       enable_backdrop ? ash::WindowBackdrop::BackdropMode::kEnabled
1314                       : ash::WindowBackdrop::BackdropMode::kAuto;
1315 
1316   ash::WindowBackdrop* window_backdrop = ash::WindowBackdrop::Get(window);
1317   if (window_backdrop->mode() != target_backdrop_mode)
1318     window_backdrop->SetBackdropMode(target_backdrop_mode);
1319 }
1320 
UpdateFrameWidth()1321 void ClientControlledShellSurface::UpdateFrameWidth() {
1322   int width = -1;
1323   if (shadow_bounds_) {
1324     float device_scale_factor =
1325         GetWidget()->GetNativeWindow()->layer()->device_scale_factor();
1326     float dsf_to_default_dsf = device_scale_factor / scale_;
1327     width = base::ClampRound(shadow_bounds_->width() * dsf_to_default_dsf);
1328   }
1329   static_cast<ash::HeaderView*>(GetFrameView()->GetHeaderView())
1330       ->SetWidthInPixels(width);
1331 }
1332 
UpdateFrameType()1333 void ClientControlledShellSurface::UpdateFrameType() {
1334   if (container_ == ash::kShellWindowId_SystemModalContainer &&
1335       pending_frame_type_ != SurfaceFrameType::NONE) {
1336     LOG(WARNING)
1337         << "A surface in system modal container should not have a frame:"
1338         << static_cast<int>(pending_frame_type_);
1339     return;
1340   }
1341 
1342   // TODO(oshima): We shouldn't send the synthesized motion event when just
1343   // changing the frame type. The better solution would be to keep the window
1344   // position regardless of the frame state, but that won't be available until
1345   // next arc version.
1346   // This is a stopgap solution not to generate the event until it is resolved.
1347   EventTargetingBlocker blocker;
1348   bool suppress_mouse_event = frame_type_ != pending_frame_type_ && widget_;
1349   if (suppress_mouse_event)
1350     blocker.Block(widget_->GetNativeWindow());
1351   ShellSurfaceBase::OnSetFrame(pending_frame_type_);
1352   UpdateAutoHideFrame();
1353 
1354   if (suppress_mouse_event)
1355     UpdateSurfaceBounds();
1356 }
1357 
1358 void ClientControlledShellSurface::
EnsureCompositorIsLockedForOrientationChange()1359     EnsureCompositorIsLockedForOrientationChange() {
1360   if (!orientation_compositor_lock_) {
1361     ui::Compositor* compositor =
1362         widget_->GetNativeWindow()->layer()->GetCompositor();
1363     orientation_compositor_lock_ = compositor->GetCompositorLock(
1364         this, base::TimeDelta::FromMilliseconds(kOrientationLockTimeoutMs));
1365   }
1366 }
1367 
GetWindowState()1368 ash::WindowState* ClientControlledShellSurface::GetWindowState() {
1369   return ash::WindowState::Get(widget_->GetNativeWindow());
1370 }
1371 
GetFrameView()1372 ash::NonClientFrameViewAsh* ClientControlledShellSurface::GetFrameView() {
1373   return static_cast<ash::NonClientFrameViewAsh*>(
1374       widget_->non_client_view()->frame_view());
1375 }
1376 
GetFrameView() const1377 const ash::NonClientFrameViewAsh* ClientControlledShellSurface::GetFrameView()
1378     const {
1379   return static_cast<const ash::NonClientFrameViewAsh*>(
1380       widget_->non_client_view()->frame_view());
1381 }
1382 
EnsurePendingScale()1383 void ClientControlledShellSurface::EnsurePendingScale() {
1384   // Handle the case where we receive bounds from the client before the initial
1385   // scale has been set.
1386   if (pending_scale_ == 0.0) {
1387     DCHECK(!use_default_scale_cancellation_);
1388     display::Display display;
1389     if (display::Screen::GetScreen()->GetDisplayWithDisplayId(
1390             pending_display_id_, &display)) {
1391       SetScale(display.device_scale_factor());
1392       CommitPendingScale();
1393     }
1394   }
1395 }
1396 
GetClientToDpPendingScale() const1397 float ClientControlledShellSurface::GetClientToDpPendingScale() const {
1398   // When the client is scale-aware, we expect that it will resize windows when
1399   // reacting to scale changes. Since we do not commit the scale until the
1400   // buffer size changes, any bounds sent after a scale change and before the
1401   // scale commit will result in mismatched sizes between widget and the buffer.
1402   // To work around this, we use pending_scale_ to calculate bounds in DP
1403   // instead of GetClientToDpScale().
1404   return use_default_scale_cancellation_ ? 1.f : 1.f / pending_scale_;
1405 }
1406 
1407 gfx::Rect
GetClientBoundsForWindowBoundsAndWindowState(const gfx::Rect & window_bounds,chromeos::WindowStateType window_state) const1408 ClientControlledShellSurface::GetClientBoundsForWindowBoundsAndWindowState(
1409     const gfx::Rect& window_bounds,
1410     chromeos::WindowStateType window_state) const {
1411   // The client's geometry uses fullscreen in client controlled,
1412   // (but the surface is placed under the frame), so just use
1413   // the window bounds instead for maximixed state.
1414   // Snapped window states in tablet mode do not include the caption height.
1415   const bool is_snapped =
1416       window_state == chromeos::WindowStateType::kLeftSnapped ||
1417       window_state == chromeos::WindowStateType::kRightSnapped;
1418   const bool is_maximized =
1419       window_state == chromeos::WindowStateType::kMaximized;
1420   const display::TabletState tablet_state =
1421       chromeos::TabletState::Get()->state();
1422   const bool is_tablet_mode = WMHelper::GetInstance()->InTabletMode();
1423 
1424   if (is_maximized || (is_snapped && is_tablet_mode))
1425     return window_bounds;
1426 
1427   gfx::Rect client_bounds =
1428       GetFrameView()->GetClientBoundsForWindowBounds(window_bounds);
1429 
1430   if (is_snapped && tablet_state == display::TabletState::kExitingTabletMode) {
1431     // Until the next commit, the frame view is in immersive mode, and the above
1432     // GetClientBoundsForWindowBounds doesn't return bounds taking the caption
1433     // height into account.
1434     client_bounds.Inset(0, GetFrameView()->NonClientTopBorderPreferredHeight(),
1435                         0, 0);
1436   }
1437   return client_bounds;
1438 }
1439 
1440 // static
1441 void ClientControlledShellSurface::
SetClientControlledStateDelegateFactoryForTest(const DelegateFactoryCallback & callback)1442     SetClientControlledStateDelegateFactoryForTest(
1443         const DelegateFactoryCallback& callback) {
1444   auto& factory = GetFactoryForTesting();
1445   factory = callback;
1446 }
1447 
1448 }  // namespace exo
1449