| Index: ash/wm/screen_pinning_controller.cc | 
| diff --git a/ash/wm/screen_pinning_controller.cc b/ash/wm/screen_pinning_controller.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..f312288ec55f36e68ed1443ed6d34799f1388a9f | 
| --- /dev/null | 
| +++ b/ash/wm/screen_pinning_controller.cc | 
| @@ -0,0 +1,403 @@ | 
| +// Copyright 2016 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "ash/wm/screen_pinning_controller.h" | 
| + | 
| +#include <algorithm> | 
| +#include <vector> | 
| + | 
| +#include "ash/aura/wm_window_aura.h" | 
| +#include "ash/common/shell_window_ids.h" | 
| +#include "ash/common/wm/window_state.h" | 
| +#include "ash/common/wm_shell_common.h" | 
| +#include "ash/common/wm_window.h" | 
| +#include "ash/common/wm_window_observer.h" | 
| +#include "ash/display/window_tree_host_manager.h" | 
| +#include "ash/shell.h" | 
| +#include "ash/wm/dim_window.h" | 
| +#include "base/auto_reset.h" | 
| +#include "base/logging.h" | 
| +#include "base/stl_util.h" | 
| +#include "ui/aura/window_observer.h" | 
| + | 
| +namespace ash { | 
| +namespace { | 
| + | 
| +WmWindow* CreateDimWindow(WmWindow* container) { | 
| +  DimWindow* dim_window = new DimWindow(WmWindowAura::GetAuraWindow(container)); | 
| +  dim_window->SetDimOpacity(1);  // Set whole black. | 
| +  WmWindow* result = WmWindowAura::Get(dim_window); | 
| +  result->SetFullscreen(); | 
| +  result->Show(); | 
| +  return result; | 
| +} | 
| + | 
| +// Returns a list of WmWindows corresponding to SystemModalContainers, | 
| +// except ones whose root is shared with |pinned_window|. | 
| +std::vector<WmWindow*> GetSystemModalWindowsExceptPinned( | 
| +    WmWindow* pinned_window) { | 
| +  WmWindow* pinned_root = pinned_window->GetRootWindow(); | 
| + | 
| +  std::vector<WmWindow*> result; | 
| +  for (WmWindow* system_modal : WmWindowAura::FromAuraWindows( | 
| +           ash::Shell::GetContainersFromAllRootWindows( | 
| +               kShellWindowId_SystemModalContainer, nullptr))) { | 
| +    if (system_modal->GetRootWindow() == pinned_root) | 
| +      continue; | 
| +    result.push_back(system_modal); | 
| +  } | 
| +  return result; | 
| +} | 
| + | 
| +void AddObserverToChildren(WmWindow* container, | 
| +                           aura::WindowObserver* observer) { | 
| +  for (WmWindow* child : container->GetChildren()) { | 
| +    WmWindowAura::GetAuraWindow(child)->AddObserver(observer); | 
| +  } | 
| +} | 
| + | 
| +void RemoveObserverFromChildren(WmWindow* container, | 
| +                                aura::WindowObserver* observer) { | 
| +  for (WmWindow* child : container->GetChildren()) { | 
| +    WmWindowAura::GetAuraWindow(child)->RemoveObserver(observer); | 
| +  } | 
| +} | 
| + | 
| +}  // namespace | 
| + | 
| +// Adapter to fire OnPinnedContainerWindowStackingChanged(). | 
| +class ScreenPinningController::PinnedContainerChildWindowObserver | 
| +    : public aura::WindowObserver { | 
| + public: | 
| +  explicit PinnedContainerChildWindowObserver( | 
| +      ScreenPinningController* controller) | 
| +      : controller_(controller) {} | 
| + | 
| +  void OnWindowStackingChanged(aura::Window* window) override { | 
| +    controller_->OnPinnedContainerWindowStackingChanged( | 
| +        WmWindowAura::Get(window)); | 
| +  } | 
| + | 
| + private: | 
| +  ScreenPinningController* controller_; | 
| +  DISALLOW_COPY_AND_ASSIGN(PinnedContainerChildWindowObserver); | 
| +}; | 
| + | 
| +// Adapter to translate OnWindowAdded/OnWillRemoveWindow for the container | 
| +// containing the pinned window, to the corresponding controller's methods. | 
| +class ScreenPinningController::PinnedContainerWindowObserver | 
| +    : public aura::WindowObserver { | 
| + public: | 
| +  explicit PinnedContainerWindowObserver(ScreenPinningController* controller) | 
| +      : controller_(controller) {} | 
| + | 
| +  void OnWindowAdded(aura::Window* new_window) override { | 
| +    controller_->OnWindowAddedToPinnedContainer(WmWindowAura::Get(new_window)); | 
| +  } | 
| +  void OnWillRemoveWindow(aura::Window* window) override { | 
| +    controller_->OnWillRemoveWindowFromPinnedContainer( | 
| +        WmWindowAura::Get(window)); | 
| +  } | 
| +  void OnWindowDestroying(aura::Window* window) override { | 
| +    // Just in case. There is nothing we can do here. | 
| +    window->RemoveObserver(this); | 
| +  } | 
| + | 
| + private: | 
| +  ScreenPinningController* controller_; | 
| +  DISALLOW_COPY_AND_ASSIGN(PinnedContainerWindowObserver); | 
| +}; | 
| + | 
| +// Adapter to fire OnSystemModalContainerWindowStackingChanged(). | 
| +class ScreenPinningController::SystemModalContainerChildWindowObserver | 
| +    : public aura::WindowObserver { | 
| + public: | 
| +  explicit SystemModalContainerChildWindowObserver( | 
| +      ScreenPinningController* controller) | 
| +      : controller_(controller) {} | 
| + | 
| +  void OnWindowStackingChanged(aura::Window* window) override { | 
| +    controller_->OnSystemModalContainerWindowStackingChanged( | 
| +        WmWindowAura::Get(window)); | 
| +  } | 
| + | 
| + private: | 
| +  ScreenPinningController* controller_; | 
| +  DISALLOW_COPY_AND_ASSIGN(SystemModalContainerChildWindowObserver); | 
| +}; | 
| + | 
| +// Adapter to translate OnWindowAdded/OnWillRemoveWindow for the | 
| +// SystemModalContainer to the corresponding controller's methods. | 
| +class ScreenPinningController::SystemModalContainerWindowObserver | 
| +    : public aura::WindowObserver { | 
| + public: | 
| +  explicit SystemModalContainerWindowObserver( | 
| +      ScreenPinningController* controller) | 
| +      : controller_(controller) {} | 
| + | 
| +  void OnWindowAdded(aura::Window* new_window) override { | 
| +    controller_->OnWindowAddedToSystemModalContainer( | 
| +        WmWindowAura::Get(new_window)); | 
| +  } | 
| +  void OnWillRemoveWindow(aura::Window* window) override { | 
| +    controller_->OnWillRemoveWindowFromSystemModalContainer( | 
| +        WmWindowAura::Get(window)); | 
| +  } | 
| +  void OnWindowDestroying(aura::Window* window) override { | 
| +    // Just in case. There is nothing we can do here. | 
| +    window->RemoveObserver(this); | 
| +  } | 
| + | 
| + private: | 
| +  ScreenPinningController* controller_; | 
| +  DISALLOW_COPY_AND_ASSIGN(SystemModalContainerWindowObserver); | 
| +}; | 
| + | 
| +// Tracks DimWindow's life. | 
| +class ScreenPinningController::DimWindowObserver : public aura::WindowObserver { | 
| + public: | 
| +  explicit DimWindowObserver(ScreenPinningController* controller) | 
| +      : controller_(controller) {} | 
| + | 
| +  void OnWindowParentChanged(aura::Window* window, | 
| +                             aura::Window* parent) override { | 
| +    // In case of parent change, there is nothing we can do for that change. | 
| +    // Also, it is unsafe to delete the moving window here, because it could | 
| +    // cause SEGV. Instead, we just hide it. | 
| +    // Note that, the window is still tracked by the ScreenPinningController, | 
| +    // so that it should be deleted on "unpin", at least. | 
| +    window->Hide(); | 
| +  } | 
| + | 
| +  void OnWindowDestroying(aura::Window* window) override { | 
| +    controller_->OnDimWindowDestroying(WmWindowAura::Get(window)); | 
| +  } | 
| + | 
| + private: | 
| +  ScreenPinningController* controller_; | 
| +  DISALLOW_COPY_AND_ASSIGN(DimWindowObserver); | 
| +}; | 
| + | 
| +ScreenPinningController::ScreenPinningController( | 
| +    WmShellCommon* wm_shell_common, | 
| +    WindowTreeHostManager* window_tree_host_manager) | 
| +    : wm_shell_common_(wm_shell_common), | 
| +      window_tree_host_manager_(window_tree_host_manager), | 
| +      pinned_container_window_observer_( | 
| +          new PinnedContainerWindowObserver(this)), | 
| +      pinned_container_child_window_observer_( | 
| +          new PinnedContainerChildWindowObserver(this)), | 
| +      system_modal_container_window_observer_( | 
| +          new SystemModalContainerWindowObserver(this)), | 
| +      system_modal_container_child_window_observer_( | 
| +          new SystemModalContainerChildWindowObserver(this)), | 
| +      dim_window_observer_(new DimWindowObserver(this)) { | 
| +  wm_shell_common_->AddShellObserver(this); | 
| +  window_tree_host_manager_->AddObserver(this); | 
| +} | 
| + | 
| +ScreenPinningController::~ScreenPinningController() { | 
| +  window_tree_host_manager_->RemoveObserver(this); | 
| +  wm_shell_common_->RemoveShellObserver(this); | 
| +} | 
| + | 
| +bool ScreenPinningController::IsPinned() const { | 
| +  return pinned_window_ != nullptr; | 
| +} | 
| + | 
| +void ScreenPinningController::OnPinnedStateChanged(WmWindow* pinned_window) { | 
| +  if (pinned_window->GetWindowState()->IsPinned()) { | 
| +    if (pinned_window_) { | 
| +      LOG(DFATAL) << "Pinned mode is enabled, while it is already in " | 
| +                  << "the pinned mode"; | 
| +      return; | 
| +    } | 
| + | 
| +    WmWindow* container = pinned_window->GetParent(); | 
| +    std::vector<WmWindow*> system_modal_containers = | 
| +        GetSystemModalWindowsExceptPinned(pinned_window); | 
| + | 
| +    // Set up the container which has the pinned window. | 
| +    pinned_window_ = pinned_window; | 
| +    background_window_ = CreateDimWindow(container); | 
| +    container->StackChildAtTop(pinned_window); | 
| +    container->StackChildBelow(background_window_, pinned_window); | 
| + | 
| +    // Set the dim windows to the system containers, other than the one which | 
| +    // the root window of the pinned window holds. | 
| +    for (WmWindow* system_modal : system_modal_containers) { | 
| +      WmWindow* dim_window = CreateDimWindow(system_modal); | 
| +      system_modal->StackChildAtBottom(dim_window); | 
| +      dim_windows_.push_back(dim_window); | 
| +      WmWindowAura::GetAuraWindow(dim_window) | 
| +          ->AddObserver(dim_window_observer_.get()); | 
| +    } | 
| + | 
| +    // Set observers. | 
| +    WmWindowAura::GetAuraWindow(container)->AddObserver( | 
| +        pinned_container_window_observer_.get()); | 
| +    AddObserverToChildren(container, | 
| +                          pinned_container_child_window_observer_.get()); | 
| +    for (WmWindow* system_modal : system_modal_containers) { | 
| +      WmWindowAura::GetAuraWindow(system_modal) | 
| +          ->AddObserver(system_modal_container_window_observer_.get()); | 
| +      AddObserverToChildren( | 
| +          system_modal, system_modal_container_child_window_observer_.get()); | 
| +    } | 
| +  } else { | 
| +    if (pinned_window != pinned_window_) { | 
| +      LOG(DFATAL) << "Pinned mode is being disabled, but for the different " | 
| +                  << "target window."; | 
| +      return; | 
| +    } | 
| + | 
| +    WmWindow* container = pinned_window->GetParent(); | 
| +    std::vector<WmWindow*> system_modal_containers = | 
| +        GetSystemModalWindowsExceptPinned(pinned_window_); | 
| + | 
| +    // Unset observers. | 
| +    for (WmWindow* system_modal : | 
| +         GetSystemModalWindowsExceptPinned(pinned_window_)) { | 
| +      RemoveObserverFromChildren( | 
| +          system_modal, system_modal_container_child_window_observer_.get()); | 
| +      WmWindowAura::GetAuraWindow(system_modal) | 
| +          ->RemoveObserver(system_modal_container_window_observer_.get()); | 
| +    } | 
| +    RemoveObserverFromChildren(container, | 
| +                               pinned_container_child_window_observer_.get()); | 
| +    WmWindowAura::GetAuraWindow(container)->RemoveObserver( | 
| +        pinned_container_window_observer_.get()); | 
| + | 
| +    // Delete the dim windows. This works, but do not use RAII, because | 
| +    // the window is owned by the parent. | 
| +    // Note: dim_windows_ will be updated during the deletion. So, copy is | 
| +    // needed. | 
| +    for (WmWindow* dim_window : std::vector<WmWindow*>(dim_windows_)) { | 
| +      delete WmWindowAura::GetAuraWindow(dim_window); | 
| +    } | 
| +    DCHECK(dim_windows_.empty()); | 
| +    delete WmWindowAura::GetAuraWindow(background_window_); | 
| +    background_window_ = nullptr; | 
| + | 
| +    pinned_window_ = nullptr; | 
| +  } | 
| +} | 
| + | 
| +void ScreenPinningController::OnWindowAddedToPinnedContainer( | 
| +    WmWindow* new_window) { | 
| +  KeepPinnedWindowOnTop(); | 
| +  WmWindowAura::GetAuraWindow(new_window) | 
| +      ->AddObserver(pinned_container_child_window_observer_.get()); | 
| +} | 
| + | 
| +void ScreenPinningController::OnWillRemoveWindowFromPinnedContainer( | 
| +    WmWindow* window) { | 
| +  WmWindowAura::GetAuraWindow(window)->RemoveObserver( | 
| +      pinned_container_child_window_observer_.get()); | 
| +  if (window == pinned_window_) { | 
| +    pinned_window_->GetWindowState()->Restore(); | 
| +    return; | 
| +  } | 
| +  if (window == background_window_) { | 
| +    background_window_ = nullptr; | 
| +    return; | 
| +  } | 
| +} | 
| + | 
| +void ScreenPinningController::OnPinnedContainerWindowStackingChanged( | 
| +    WmWindow* window) { | 
| +  KeepPinnedWindowOnTop(); | 
| +} | 
| + | 
| +void ScreenPinningController::OnWindowAddedToSystemModalContainer( | 
| +    WmWindow* new_window) { | 
| +  KeepDimWindowAtBottom(new_window->GetParent()); | 
| +  WmWindowAura::GetAuraWindow(new_window) | 
| +      ->AddObserver(system_modal_container_child_window_observer_.get()); | 
| +} | 
| + | 
| +void ScreenPinningController::OnWillRemoveWindowFromSystemModalContainer( | 
| +    WmWindow* window) { | 
| +  WmWindowAura::GetAuraWindow(window)->RemoveObserver( | 
| +      system_modal_container_child_window_observer_.get()); | 
| +} | 
| + | 
| +void ScreenPinningController::OnSystemModalContainerWindowStackingChanged( | 
| +    WmWindow* window) { | 
| +  KeepDimWindowAtBottom(window->GetParent()); | 
| +} | 
| + | 
| +void ScreenPinningController::OnDimWindowDestroying(WmWindow* window) { | 
| +  dim_windows_.erase( | 
| +      std::find(dim_windows_.begin(), dim_windows_.end(), window)); | 
| +} | 
| + | 
| +void ScreenPinningController::OnDisplayConfigurationChanged() { | 
| +  // Note: this is called on display attached or detached. | 
| +  if (!IsPinned()) | 
| +    return; | 
| + | 
| +  // On display detaching, all necessary windows are transfered to the | 
| +  // primary display's tree, and called this. | 
| +  // So, delete the dim windows which are not a part of target system modal | 
| +  // container. | 
| +  // On display attaching, the new system modal container does not have the | 
| +  // dim window. So create it. | 
| + | 
| +  // First, delete unnecessary dim windows. | 
| +  // The delete will update dim_windows_, so create the copy is needed. | 
| +  for (WmWindow* dim_window : std::vector<WmWindow*>(dim_windows_)) { | 
| +    if (!dim_window->GetTargetVisibility()) | 
| +      delete WmWindowAura::GetAuraWindow(dim_window); | 
| +  } | 
| + | 
| +  // Then, create missing dim_windows. | 
| +  std::vector<WmWindow*> system_modal_containers = | 
| +      GetSystemModalWindowsExceptPinned(pinned_window_); | 
| +  for (WmWindow* system_modal : system_modal_containers) { | 
| +    const std::vector<WmWindow*> children = system_modal->GetChildren(); | 
| +    if (!children.empty() && ContainsValue(dim_windows_, children[0])) { | 
| +      // The system modal dialog has the dim window. | 
| +      continue; | 
| +    } | 
| + | 
| +    // This is the new system modal dialog. | 
| +    WmWindow* dim_window = CreateDimWindow(system_modal); | 
| +    system_modal->StackChildAtBottom(dim_window); | 
| +    dim_windows_.push_back(dim_window); | 
| +    WmWindowAura::GetAuraWindow(dim_window) | 
| +        ->AddObserver(dim_window_observer_.get()); | 
| + | 
| +    // Set observers to the tree. | 
| +    WmWindowAura::GetAuraWindow(system_modal) | 
| +        ->AddObserver(system_modal_container_window_observer_.get()); | 
| +    AddObserverToChildren(system_modal, | 
| +                          system_modal_container_child_window_observer_.get()); | 
| +  } | 
| +} | 
| + | 
| +void ScreenPinningController::KeepPinnedWindowOnTop() { | 
| +  if (in_restacking_) | 
| +    return; | 
| + | 
| +  base::AutoReset<bool> auto_reset(&in_restacking_, true); | 
| +  WmWindow* container = pinned_window_->GetParent(); | 
| +  container->StackChildAtTop(pinned_window_); | 
| +  container->StackChildBelow(background_window_, pinned_window_); | 
| +} | 
| + | 
| +void ScreenPinningController::KeepDimWindowAtBottom(WmWindow* container) { | 
| +  if (in_restacking_) | 
| +    return; | 
| + | 
| +  base::AutoReset<bool> auto_reset(&in_restacking_, true); | 
| +  for (WmWindow* dim_window : dim_windows_) { | 
| +    if (dim_window->GetParent() == container) { | 
| +      container->StackChildAtBottom(dim_window); | 
| +      break; | 
| +    } | 
| +  } | 
| +} | 
| + | 
| +}  // namespace ash | 
|  |