1 // Copyright 2015 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/shell_surface.h"
6 
7 #include "ash/public/cpp/shell_window_ids.h"
8 #include "ash/public/cpp/window_state_type.h"
9 #include "ash/shell.h"
10 #include "ash/wm/desks/desks_util.h"
11 #include "ash/wm/toplevel_window_event_handler.h"
12 #include "ash/wm/window_resizer.h"
13 #include "ash/wm/window_state.h"
14 #include "base/bind.h"
15 #include "base/logging.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "components/exo/shell_surface_util.h"
18 #include "ui/aura/client/aura_constants.h"
19 #include "ui/aura/client/cursor_client.h"
20 #include "ui/aura/env.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_event_dispatcher.h"
23 #include "ui/aura/window_tree_host.h"
24 #include "ui/base/ui_base_types.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/wm/core/coordinate_conversion.h"
27 #include "ui/wm/core/transient_window_manager.h"
28 #include "ui/wm/core/window_util.h"
29 
30 namespace exo {
31 namespace {
32 
33 // Maximum amount of time to wait for contents after a change to maximize,
34 // fullscreen or pinned state.
35 constexpr int kMaximizedOrFullscreenOrPinnedLockTimeoutMs = 100;
36 
37 }  // namespace
38 
39 ////////////////////////////////////////////////////////////////////////////////
40 // ShellSurface, ScopedAnimationsDisabled:
41 
42 // Helper class used to temporarily disable animations. Restores the
43 // animations disabled property when instance is destroyed.
44 class ShellSurface::ScopedAnimationsDisabled {
45  public:
46   explicit ScopedAnimationsDisabled(ShellSurface* shell_surface);
47   ~ScopedAnimationsDisabled();
48 
49  private:
50   ShellSurface* const shell_surface_;
51   bool saved_animations_disabled_ = false;
52 
53   DISALLOW_COPY_AND_ASSIGN(ScopedAnimationsDisabled);
54 };
55 
ScopedAnimationsDisabled(ShellSurface * shell_surface)56 ShellSurface::ScopedAnimationsDisabled::ScopedAnimationsDisabled(
57     ShellSurface* shell_surface)
58     : shell_surface_(shell_surface) {
59   if (shell_surface_->widget_) {
60     aura::Window* window = shell_surface_->widget_->GetNativeWindow();
61     saved_animations_disabled_ =
62         window->GetProperty(aura::client::kAnimationsDisabledKey);
63     window->SetProperty(aura::client::kAnimationsDisabledKey, true);
64   }
65 }
66 
~ScopedAnimationsDisabled()67 ShellSurface::ScopedAnimationsDisabled::~ScopedAnimationsDisabled() {
68   if (shell_surface_->widget_) {
69     aura::Window* window = shell_surface_->widget_->GetNativeWindow();
70     DCHECK_EQ(window->GetProperty(aura::client::kAnimationsDisabledKey), true);
71     window->SetProperty(aura::client::kAnimationsDisabledKey,
72                         saved_animations_disabled_);
73   }
74 }
75 
76 ////////////////////////////////////////////////////////////////////////////////
77 // ShellSurface, Config:
78 
79 // Surface state associated with each configure request.
80 struct ShellSurface::Config {
81   Config(uint32_t serial,
82          const gfx::Vector2d& origin_offset,
83          int resize_component,
84          std::unique_ptr<ui::CompositorLock> compositor_lock);
85   ~Config() = default;
86 
87   uint32_t serial;
88   gfx::Vector2d origin_offset;
89   int resize_component;
90   std::unique_ptr<ui::CompositorLock> compositor_lock;
91 };
92 
Config(uint32_t serial,const gfx::Vector2d & origin_offset,int resize_component,std::unique_ptr<ui::CompositorLock> compositor_lock)93 ShellSurface::Config::Config(
94     uint32_t serial,
95     const gfx::Vector2d& origin_offset,
96     int resize_component,
97     std::unique_ptr<ui::CompositorLock> compositor_lock)
98     : serial(serial),
99       origin_offset(origin_offset),
100       resize_component(resize_component),
101       compositor_lock(std::move(compositor_lock)) {}
102 
103 ////////////////////////////////////////////////////////////////////////////////
104 // ShellSurface, ScopedConfigure:
105 
ScopedConfigure(ShellSurface * shell_surface,bool force_configure)106 ShellSurface::ScopedConfigure::ScopedConfigure(ShellSurface* shell_surface,
107                                                bool force_configure)
108     : shell_surface_(shell_surface), force_configure_(force_configure) {
109   // ScopedConfigure instances cannot be nested.
110   DCHECK(!shell_surface_->scoped_configure_);
111   shell_surface_->scoped_configure_ = this;
112 }
113 
~ScopedConfigure()114 ShellSurface::ScopedConfigure::~ScopedConfigure() {
115   DCHECK_EQ(shell_surface_->scoped_configure_, this);
116   shell_surface_->scoped_configure_ = nullptr;
117   if (needs_configure_ || force_configure_)
118     shell_surface_->Configure();
119   // ScopedConfigure instance might have suppressed a widget bounds update.
120   if (shell_surface_->widget_) {
121     shell_surface_->UpdateWidgetBounds();
122     shell_surface_->UpdateShadow();
123   }
124 }
125 
126 ////////////////////////////////////////////////////////////////////////////////
127 // ShellSurface, public:
128 
ShellSurface(Surface * surface,const gfx::Point & origin,bool activatable,bool can_minimize,int container)129 ShellSurface::ShellSurface(Surface* surface,
130                            const gfx::Point& origin,
131                            bool activatable,
132                            bool can_minimize,
133                            int container)
134     : ShellSurfaceBase(surface, origin, activatable, can_minimize, container) {}
135 
ShellSurface(Surface * surface)136 ShellSurface::ShellSurface(Surface* surface)
137     : ShellSurfaceBase(surface,
138                        gfx::Point(),
139                        true,
140                        true,
141                        ash::desks_util::GetActiveDeskContainerId()) {}
142 
~ShellSurface()143 ShellSurface::~ShellSurface() {
144   DCHECK(!scoped_configure_);
145   if (widget_)
146     ash::WindowState::Get(widget_->GetNativeWindow())->RemoveObserver(this);
147 }
148 
AcknowledgeConfigure(uint32_t serial)149 void ShellSurface::AcknowledgeConfigure(uint32_t serial) {
150   TRACE_EVENT1("exo", "ShellSurface::AcknowledgeConfigure", "serial", serial);
151 
152   // Apply all configs that are older or equal to |serial|. The result is that
153   // the origin of the main surface will move and the resize direction will
154   // change to reflect the acknowledgement of configure request with |serial|
155   // at the next call to Commit().
156   while (!pending_configs_.empty()) {
157     std::unique_ptr<Config> config = std::move(pending_configs_.front());
158     pending_configs_.pop_front();
159 
160     // Add the config offset to the accumulated offset that will be applied when
161     // Commit() is called.
162     pending_origin_offset_ += config->origin_offset;
163 
164     // Set the resize direction that will be applied when Commit() is called.
165     pending_resize_component_ = config->resize_component;
166 
167     if (config->serial == serial)
168       break;
169   }
170 
171   if (widget_) {
172     UpdateWidgetBounds();
173     UpdateShadow();
174   }
175 }
176 
SetParent(ShellSurface * parent)177 void ShellSurface::SetParent(ShellSurface* parent) {
178   TRACE_EVENT1("exo", "ShellSurface::SetParent", "parent",
179                parent ? base::UTF16ToASCII(parent->title_) : "null");
180 
181   SetParentWindow(parent ? parent->GetWidget()->GetNativeWindow() : nullptr);
182 }
183 
Maximize()184 void ShellSurface::Maximize() {
185   TRACE_EVENT0("exo", "ShellSurface::Maximize");
186 
187   if (!widget_) {
188     initial_show_state_ = ui::SHOW_STATE_MAXIMIZED;
189     return;
190   }
191 
192   // Note: This will ask client to configure its surface even if already
193   // maximized.
194   ScopedConfigure scoped_configure(this, true);
195   widget_->Maximize();
196 }
197 
Minimize()198 void ShellSurface::Minimize() {
199   TRACE_EVENT0("exo", "ShellSurface::Minimize");
200 
201   if (!widget_) {
202     initial_show_state_ = ui::SHOW_STATE_MINIMIZED;
203     return;
204   }
205 
206   // Note: This will ask client to configure its surface even if already
207   // minimized.
208   ScopedConfigure scoped_configure(this, true);
209   widget_->Minimize();
210 }
211 
Restore()212 void ShellSurface::Restore() {
213   TRACE_EVENT0("exo", "ShellSurface::Restore");
214 
215   if (!widget_) {
216     initial_show_state_ = ui::SHOW_STATE_NORMAL;
217     return;
218   }
219 
220   // Note: This will ask client to configure its surface even if not already
221   // maximized or minimized.
222   ScopedConfigure scoped_configure(this, true);
223   widget_->Restore();
224 }
225 
SetFullscreen(bool fullscreen)226 void ShellSurface::SetFullscreen(bool fullscreen) {
227   TRACE_EVENT1("exo", "ShellSurface::SetFullscreen", "fullscreen", fullscreen);
228 
229   if (!widget_) {
230     initial_show_state_ = ui::SHOW_STATE_FULLSCREEN;
231     return;
232   }
233 
234   // Note: This will ask client to configure its surface even if fullscreen
235   // state doesn't change.
236   ScopedConfigure scoped_configure(this, true);
237   widget_->SetFullscreen(fullscreen);
238 }
239 
SetPopup()240 void ShellSurface::SetPopup() {
241   DCHECK(!widget_);
242   is_popup_ = true;
243 }
244 
Grab()245 void ShellSurface::Grab() {
246   DCHECK(is_popup_);
247   DCHECK(!widget_);
248   has_grab_ = true;
249 }
250 
StartMove()251 void ShellSurface::StartMove() {
252   TRACE_EVENT0("exo", "ShellSurface::StartMove");
253 
254   if (!widget_)
255     return;
256 
257   AttemptToStartDrag(HTCAPTION);
258 }
259 
StartResize(int component)260 void ShellSurface::StartResize(int component) {
261   TRACE_EVENT1("exo", "ShellSurface::StartResize", "component", component);
262 
263   if (!widget_)
264     return;
265 
266   AttemptToStartDrag(component);
267 }
268 
ShouldAutoMaximize()269 bool ShellSurface::ShouldAutoMaximize() {
270   // Unless a child class overrides the behaviour, we will never auto-maximize.
271   return false;
272 }
273 
274 ////////////////////////////////////////////////////////////////////////////////
275 // SurfaceDelegate overrides:
276 
OnSetParent(Surface * parent,const gfx::Point & position)277 void ShellSurface::OnSetParent(Surface* parent, const gfx::Point& position) {
278   views::Widget* parent_widget =
279       parent ? views::Widget::GetTopLevelWidgetForNativeView(parent->window())
280              : nullptr;
281   if (parent_widget) {
282     // Set parent window if using one of the desks container and the container
283     // itself is not the parent.
284     if (ash::desks_util::IsDeskContainerId(container_))
285       SetParentWindow(parent_widget->GetNativeWindow());
286 
287     origin_ = position;
288     views::View::ConvertPointToScreen(
289         parent_widget->widget_delegate()->GetContentsView(), &origin_);
290 
291     if (!widget_)
292       return;
293 
294     ash::WindowState* window_state =
295         ash::WindowState::Get(widget_->GetNativeWindow());
296     if (window_state->is_dragged())
297       return;
298 
299     gfx::Rect widget_bounds = widget_->GetWindowBoundsInScreen();
300     gfx::Rect new_widget_bounds(origin_, widget_bounds.size());
301     if (new_widget_bounds != widget_bounds) {
302       base::AutoReset<bool> auto_ignore_window_bounds_changes(
303           &ignore_window_bounds_changes_, true);
304       widget_->SetBounds(new_widget_bounds);
305       UpdateSurfaceBounds();
306     }
307   } else {
308     SetParentWindow(nullptr);
309   }
310 }
311 
312 ////////////////////////////////////////////////////////////////////////////////
313 // ShellSurfaceBase overrides:
314 
InitializeWindowState(ash::WindowState * window_state)315 void ShellSurface::InitializeWindowState(ash::WindowState* window_state) {
316   window_state->AddObserver(this);
317   window_state->set_allow_set_bounds_direct(movement_disabled_);
318   window_state->set_ignore_keyboard_bounds_change(movement_disabled_);
319   widget_->set_movement_disabled(movement_disabled_);
320 
321   // If this window is a child of some window, it should be made transient.
322   MaybeMakeTransient();
323 }
324 
GetWidgetBounds() const325 base::Optional<gfx::Rect> ShellSurface::GetWidgetBounds() const {
326   // Defer if configure requests are pending.
327   if (!pending_configs_.empty() || scoped_configure_)
328     return base::nullopt;
329 
330   gfx::Rect visible_bounds = GetVisibleBounds();
331   gfx::Rect new_widget_bounds =
332       widget_->non_client_view()
333           ? widget_->non_client_view()->GetWindowBoundsForClientBounds(
334                 visible_bounds)
335           : visible_bounds;
336 
337   if (movement_disabled_) {
338     new_widget_bounds.set_origin(origin_);
339   } else if (resize_component_ == HTCAPTION) {
340     // Preserve widget position.
341     new_widget_bounds.set_origin(widget_->GetWindowBoundsInScreen().origin());
342   } else {
343     // Compute widget origin using surface origin if the current location of
344     // surface is being anchored to one side of the widget as a result of a
345     // resize operation.
346     gfx::Rect visible_bounds = GetVisibleBounds();
347     gfx::Point origin = GetSurfaceOrigin() + visible_bounds.OffsetFromOrigin();
348     wm::ConvertPointToScreen(widget_->GetNativeWindow(), &origin);
349     new_widget_bounds.set_origin(origin);
350   }
351   return new_widget_bounds;
352 }
353 
GetSurfaceOrigin() const354 gfx::Point ShellSurface::GetSurfaceOrigin() const {
355   DCHECK(!movement_disabled_ || resize_component_ == HTCAPTION);
356 
357   gfx::Rect visible_bounds = GetVisibleBounds();
358   gfx::Rect client_bounds = GetClientViewBounds();
359 
360   switch (resize_component_) {
361     case HTCAPTION:
362       return gfx::Point() + origin_offset_ - visible_bounds.OffsetFromOrigin();
363     case HTBOTTOM:
364     case HTRIGHT:
365     case HTBOTTOMRIGHT:
366       return gfx::Point() - visible_bounds.OffsetFromOrigin();
367     case HTTOP:
368     case HTTOPRIGHT:
369       return gfx::Point(0, client_bounds.height() - visible_bounds.height()) -
370              visible_bounds.OffsetFromOrigin();
371     case HTLEFT:
372     case HTBOTTOMLEFT:
373       return gfx::Point(client_bounds.width() - visible_bounds.width(), 0) -
374              visible_bounds.OffsetFromOrigin();
375     case HTTOPLEFT:
376       return gfx::Point(client_bounds.width() - visible_bounds.width(),
377                         client_bounds.height() - visible_bounds.height()) -
378              visible_bounds.OffsetFromOrigin();
379     default:
380       NOTREACHED();
381       return gfx::Point();
382   }
383 }
384 
385 ////////////////////////////////////////////////////////////////////////////////
386 // aura::WindowObserver overrides:
387 
OnWindowBoundsChanged(aura::Window * window,const gfx::Rect & old_bounds,const gfx::Rect & new_bounds,ui::PropertyChangeReason reason)388 void ShellSurface::OnWindowBoundsChanged(aura::Window* window,
389                                          const gfx::Rect& old_bounds,
390                                          const gfx::Rect& new_bounds,
391                                          ui::PropertyChangeReason reason) {
392   if (!widget_ || !root_surface() || ignore_window_bounds_changes_)
393     return;
394 
395   if (window == widget_->GetNativeWindow()) {
396     if (new_bounds.size() == old_bounds.size())
397       return;
398 
399     // If size changed then give the client a chance to produce new contents
400     // before origin on screen is changed. Retain the old origin by reverting
401     // the origin delta until the next configure is acknowledged.
402     gfx::Vector2d delta = new_bounds.origin() - old_bounds.origin();
403     origin_offset_ -= delta;
404     pending_origin_offset_accumulator_ += delta;
405 
406     UpdateSurfaceBounds();
407 
408     // The shadow size may be updated to match the widget. Change it back
409     // to the shadow content size. Note that this relies on wm::ShadowController
410     // being notified of the change before |this|.
411     UpdateShadow();
412 
413     Configure();
414   }
415 }
416 
417 ////////////////////////////////////////////////////////////////////////////////
418 // ash::WindowStateObserver overrides:
419 
OnPreWindowStateTypeChange(ash::WindowState * window_state,ash::WindowStateType old_type)420 void ShellSurface::OnPreWindowStateTypeChange(ash::WindowState* window_state,
421                                               ash::WindowStateType old_type) {
422   ash::WindowStateType new_type = window_state->GetStateType();
423   if (ash::IsMinimizedWindowStateType(old_type) ||
424       ash::IsMinimizedWindowStateType(new_type)) {
425     return;
426   }
427 
428   if (ash::IsMaximizedOrFullscreenOrPinnedWindowStateType(old_type) ||
429       ash::IsMaximizedOrFullscreenOrPinnedWindowStateType(new_type)) {
430     if (!widget_)
431       return;
432     // When transitioning in/out of maximized or fullscreen mode, we need to
433     // make sure we have a configure callback before we allow the default
434     // cross-fade animations. The configure callback provides a mechanism for
435     // the client to inform us that a frame has taken the state change into
436     // account, and without this cross-fade animations are unreliable.
437     if (!configure_callback_.is_null()) {
438       // Give client a chance to produce a frame that takes state change into
439       // account by acquiring a compositor lock.
440       ui::Compositor* compositor =
441           widget_->GetNativeWindow()->layer()->GetCompositor();
442       configure_compositor_lock_ = compositor->GetCompositorLock(
443           nullptr, base::TimeDelta::FromMilliseconds(
444                        kMaximizedOrFullscreenOrPinnedLockTimeoutMs));
445     } else {
446       scoped_animations_disabled_ =
447           std::make_unique<ScopedAnimationsDisabled>(this);
448     }
449   }
450 }
451 
OnPostWindowStateTypeChange(ash::WindowState * window_state,ash::WindowStateType old_type)452 void ShellSurface::OnPostWindowStateTypeChange(ash::WindowState* window_state,
453                                                ash::WindowStateType old_type) {
454   ash::WindowStateType new_type = window_state->GetStateType();
455   if (ash::IsMaximizedOrFullscreenOrPinnedWindowStateType(new_type)) {
456     Configure();
457   }
458 
459   if (widget_) {
460     UpdateWidgetBounds();
461     UpdateShadow();
462   }
463 
464   // Re-enable animations if they were disabled in pre state change handler.
465   scoped_animations_disabled_.reset();
466 }
467 
468 ////////////////////////////////////////////////////////////////////////////////
469 // wm::ActivationChangeObserver overrides:
470 
OnWindowActivated(ActivationReason reason,aura::Window * gained_active,aura::Window * lost_active)471 void ShellSurface::OnWindowActivated(ActivationReason reason,
472                                      aura::Window* gained_active,
473                                      aura::Window* lost_active) {
474   ShellSurfaceBase::OnWindowActivated(reason, gained_active, lost_active);
475 
476   if (!widget_)
477     return;
478 
479   if (gained_active == widget_->GetNativeWindow() ||
480       lost_active == widget_->GetNativeWindow()) {
481     Configure();
482   }
483 }
484 
485 ////////////////////////////////////////////////////////////////////////////////
486 // ShellSurfaceBase overrides:
487 
SetWidgetBounds(const gfx::Rect & bounds)488 void ShellSurface::SetWidgetBounds(const gfx::Rect& bounds) {
489   if (bounds == widget_->GetWindowBoundsInScreen())
490     return;
491 
492   // Set |ignore_window_bounds_changes_| as this change to window bounds
493   // should not result in a configure request.
494   DCHECK(!ignore_window_bounds_changes_);
495   ignore_window_bounds_changes_ = true;
496 
497   widget_->SetBounds(bounds);
498   UpdateSurfaceBounds();
499 
500   ignore_window_bounds_changes_ = false;
501 }
502 
OnPreWidgetCommit()503 bool ShellSurface::OnPreWidgetCommit() {
504   if (!widget_ && GetEnabled()) {
505     // Defer widget creation and commit until surface has contents.
506     if (host_window()->bounds().IsEmpty() &&
507         root_surface()->surface_hierarchy_content_bounds().IsEmpty()) {
508       Configure();
509       return false;
510     }
511 
512     // Allow the window to maximize itself on launch.
513     if (ShouldAutoMaximize())
514       initial_show_state_ = ui::SHOW_STATE_MAXIMIZED;
515 
516     CreateShellSurfaceWidget(initial_show_state_);
517   }
518 
519   // Apply the accumulated pending origin offset to reflect acknowledged
520   // configure requests.
521   origin_offset_ += pending_origin_offset_;
522   pending_origin_offset_ = gfx::Vector2d();
523 
524   // Update resize direction to reflect acknowledged configure requests.
525   resize_component_ = pending_resize_component_;
526 
527   return true;
528 }
529 
OnPostWidgetCommit()530 void ShellSurface::OnPostWidgetCommit() {}
531 
532 ////////////////////////////////////////////////////////////////////////////////
533 // ShellSurface, private:
534 
SetParentWindow(aura::Window * parent)535 void ShellSurface::SetParentWindow(aura::Window* parent) {
536   if (parent_) {
537     parent_->RemoveObserver(this);
538     if (widget_) {
539       aura::Window* child_window = widget_->GetNativeWindow();
540       wm::TransientWindowManager::GetOrCreate(child_window)
541           ->set_parent_controls_visibility(false);
542       wm::RemoveTransientChild(parent_, child_window);
543     }
544   }
545   parent_ = parent;
546   if (parent_) {
547     parent_->AddObserver(this);
548     MaybeMakeTransient();
549   }
550 
551   // If |parent_| is set effects the ability to maximize the window.
552   if (widget_)
553     widget_->OnSizeConstraintsChanged();
554 }
555 
MaybeMakeTransient()556 void ShellSurface::MaybeMakeTransient() {
557   if (!parent_ || !widget_)
558     return;
559   aura::Window* child_window = widget_->GetNativeWindow();
560   wm::AddTransientChild(parent_, child_window);
561   // In the case of activatable non-popups, we also want the parent to control
562   // the child's visibility.
563   if (!widget_->is_top_level() || !widget_->CanActivate())
564     return;
565   wm::TransientWindowManager::GetOrCreate(child_window)
566       ->set_parent_controls_visibility(true);
567 }
568 
Configure(bool ends_drag)569 void ShellSurface::Configure(bool ends_drag) {
570   // Delay configure callback if |scoped_configure_| is set.
571   if (scoped_configure_) {
572     scoped_configure_->set_needs_configure();
573     return;
574   }
575 
576   gfx::Vector2d origin_offset = pending_origin_offset_accumulator_;
577   pending_origin_offset_accumulator_ = gfx::Vector2d();
578 
579   auto* window_state =
580       widget_ ? ash::WindowState::Get(widget_->GetNativeWindow()) : nullptr;
581   int resize_component = HTCAPTION;
582   // If surface is being resized, save the resize direction.
583   if (window_state && window_state->is_dragged() && !ends_drag)
584     resize_component = window_state->drag_details()->window_component;
585 
586   uint32_t serial = 0;
587   if (!configure_callback_.is_null()) {
588     if (window_state) {
589       serial = configure_callback_.Run(
590           GetClientViewBounds().size(), window_state->GetStateType(),
591           IsResizing(), widget_->IsActive(), origin_offset);
592     } else {
593       serial =
594           configure_callback_.Run(gfx::Size(), ash::WindowStateType::kNormal,
595                                   false, false, origin_offset);
596     }
597   }
598 
599   if (!serial) {
600     pending_origin_offset_ += origin_offset;
601     pending_resize_component_ = resize_component;
602     return;
603   }
604 
605   // Apply origin offset and resize component at the first Commit() after this
606   // configure request has been acknowledged.
607   pending_configs_.push_back(
608       std::make_unique<Config>(serial, origin_offset, resize_component,
609                                std::move(configure_compositor_lock_)));
610   LOG_IF(WARNING, pending_configs_.size() > 100)
611       << "Number of pending configure acks for shell surface has reached: "
612       << pending_configs_.size();
613 }
614 
AttemptToStartDrag(int component)615 void ShellSurface::AttemptToStartDrag(int component) {
616   ash::WindowState* window_state =
617       ash::WindowState::Get(widget_->GetNativeWindow());
618 
619   // Ignore if surface is already being dragged.
620   if (window_state->is_dragged())
621     return;
622 
623   aura::Window* target = widget_->GetNativeWindow();
624   ash::ToplevelWindowEventHandler* toplevel_handler =
625       ash::Shell::Get()->toplevel_window_event_handler();
626   aura::Window* mouse_pressed_handler =
627       target->GetHost()->dispatcher()->mouse_pressed_handler();
628   // Start dragging only if:
629   // 1) touch guesture is in progress.
630   // 2) mouse was pressed on the target or its subsurfaces.
631   aura::Window* gesture_target = toplevel_handler->gesture_target();
632   if (!gesture_target && !mouse_pressed_handler &&
633       target->Contains(mouse_pressed_handler)) {
634     return;
635   }
636   auto end_drag = [](ShellSurface* shell_surface,
637                      ash::ToplevelWindowEventHandler::DragResult result) {
638     shell_surface->EndDrag();
639   };
640 
641   if (gesture_target) {
642     gfx::PointF location = toplevel_handler->event_location_in_gesture_target();
643     aura::Window::ConvertPointToTarget(
644         gesture_target, widget_->GetNativeWindow()->GetRootWindow(), &location);
645     toplevel_handler->AttemptToStartDrag(
646         target, location, component,
647         base::BindOnce(end_drag, base::Unretained(this)));
648   } else {
649     gfx::Point location = aura::Env::GetInstance()->last_mouse_location();
650     ::wm::ConvertPointFromScreen(widget_->GetNativeWindow()->GetRootWindow(),
651                                  &location);
652     toplevel_handler->AttemptToStartDrag(
653         target, gfx::PointF(location), component,
654         base::BindOnce(end_drag, base::Unretained(this)));
655   }
656   // Notify client that resizing state has changed.
657   if (IsResizing())
658     Configure();
659 }
660 
EndDrag()661 void ShellSurface::EndDrag() {
662   if (resize_component_ != HTCAPTION) {
663     Configure(/*ends_drag=*/true);
664   }
665 }
666 
667 }  // namespace exo
668