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