// Copyright 2014 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 "services/ui/public/cpp/window.h"

#include <stddef.h>
#include <stdint.h>

#include <set>
#include <string>

#include "base/bind.h"
#include "base/macros.h"
#include "services/ui/common/transient_window_utils.h"
#include "services/ui/public/cpp/property_type_converters.h"
#include "services/ui/public/cpp/surface_id_handler.h"
#include "services/ui/public/cpp/window_compositor_frame_sink.h"
#include "services/ui/public/cpp/window_observer.h"
#include "services/ui/public/cpp/window_private.h"
#include "services/ui/public/cpp/window_property.h"
#include "services/ui/public/cpp/window_tracker.h"
#include "services/ui/public/cpp/window_tree_client.h"
#include "services/ui/public/interfaces/window_manager.mojom.h"
#include "ui/display/display.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

namespace ui {

namespace {

void NotifyWindowTreeChangeAtReceiver(
    Window* receiver,
    const WindowObserver::TreeChangeParams& params,
    bool change_applied) {
  WindowObserver::TreeChangeParams local_params = params;
  local_params.receiver = receiver;
  if (change_applied) {
    for (auto& observer : *WindowPrivate(receiver).observers())
      observer.OnTreeChanged(local_params);
  } else {
    for (auto& observer : *WindowPrivate(receiver).observers())
      observer.OnTreeChanging(local_params);
  }
}

void NotifyWindowTreeChangeUp(Window* start_at,
                              const WindowObserver::TreeChangeParams& params,
                              bool change_applied) {
  for (Window* current = start_at; current; current = current->parent())
    NotifyWindowTreeChangeAtReceiver(current, params, change_applied);
}

void NotifyWindowTreeChangeDown(Window* start_at,
                                const WindowObserver::TreeChangeParams& params,
                                bool change_applied) {
  NotifyWindowTreeChangeAtReceiver(start_at, params, change_applied);
  Window::Children::const_iterator it = start_at->children().begin();
  for (; it != start_at->children().end(); ++it)
    NotifyWindowTreeChangeDown(*it, params, change_applied);
}

void NotifyWindowTreeChange(const WindowObserver::TreeChangeParams& params,
                            bool change_applied) {
  NotifyWindowTreeChangeDown(params.target, params, change_applied);
  if (params.old_parent)
    NotifyWindowTreeChangeUp(params.old_parent, params, change_applied);
  if (params.new_parent)
    NotifyWindowTreeChangeUp(params.new_parent, params, change_applied);
}

class ScopedTreeNotifier {
 public:
  ScopedTreeNotifier(Window* target, Window* old_parent, Window* new_parent) {
    params_.target = target;
    params_.old_parent = old_parent;
    params_.new_parent = new_parent;
    NotifyWindowTreeChange(params_, false);
  }
  ~ScopedTreeNotifier() { NotifyWindowTreeChange(params_, true); }

 private:
  WindowObserver::TreeChangeParams params_;

  DISALLOW_COPY_AND_ASSIGN(ScopedTreeNotifier);
};

void RemoveChildImpl(Window* child, Window::Children* children) {
  Window::Children::iterator it =
      std::find(children->begin(), children->end(), child);
  if (it != children->end()) {
    children->erase(it);
    WindowPrivate(child).ClearParent();
  }
}

class OrderChangedNotifier {
 public:
  OrderChangedNotifier(Window* window,
                       Window* relative_window,
                       mojom::OrderDirection direction)
      : window_(window),
        relative_window_(relative_window),
        direction_(direction) {}

  ~OrderChangedNotifier() {}

  void NotifyWindowReordering() {
    for (auto& observer : *WindowPrivate(window_).observers())
      observer.OnWindowReordering(window_, relative_window_, direction_);
  }

  void NotifyWindowReordered() {
    for (auto& observer : *WindowPrivate(window_).observers())
      observer.OnWindowReordered(window_, relative_window_, direction_);
  }

 private:
  Window* window_;
  Window* relative_window_;
  mojom::OrderDirection direction_;

  DISALLOW_COPY_AND_ASSIGN(OrderChangedNotifier);
};

class ScopedSetBoundsNotifier {
 public:
  ScopedSetBoundsNotifier(Window* window,
                          const gfx::Rect& old_bounds,
                          const gfx::Rect& new_bounds)
      : window_(window), old_bounds_(old_bounds), new_bounds_(new_bounds) {
    for (auto& observer : *WindowPrivate(window_).observers())
      observer.OnWindowBoundsChanging(window_, old_bounds_, new_bounds_);
  }
  ~ScopedSetBoundsNotifier() {
    for (auto& observer : *WindowPrivate(window_).observers())
      observer.OnWindowBoundsChanged(window_, old_bounds_, new_bounds_);
  }

 private:
  Window* window_;
  const gfx::Rect old_bounds_;
  const gfx::Rect new_bounds_;

  DISALLOW_COPY_AND_ASSIGN(ScopedSetBoundsNotifier);
};

bool IsClientRoot(Window* window) {
  return window->window_tree() &&
         window->window_tree()->GetRoots().count(window) > 0;
}

bool WasCreatedByThisClientOrIsRoot(Window* window) {
  return window->WasCreatedByThisClient() || IsClientRoot(window);
}

void EmptyEmbedCallback(bool result) {}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// Window, public:

void Window::Destroy() {
  if (!WasCreatedByThisClientOrIsRoot(this))
    return;

  if (client_)
    client_->DestroyWindow(this);
  while (!children_.empty()) {
    Window* child = children_.front();
    if (!child->WasCreatedByThisClient()) {
      WindowPrivate(child).ClearParent();
      children_.erase(children_.begin());
    } else {
      child->Destroy();
      DCHECK(std::find(children_.begin(), children_.end(), child) ==
             children_.end());
    }
  }
  LocalDestroy();
}

bool Window::WasCreatedByThisClient() const {
  return !client_ || client_->WasCreatedByThisClient(this);
}

void Window::SetBounds(const gfx::Rect& bounds) {
  if (!WasCreatedByThisClientOrIsRoot(this))
    return;
  if (bounds_ == bounds)
    return;
  if (client_)
    client_->SetBounds(this, bounds_, bounds);
  LocalSetBounds(bounds_, bounds);
}

gfx::Rect Window::GetBoundsInRoot() const {
  gfx::Vector2d offset;
  for (const Window* w = parent(); w != nullptr; w = w->parent())
    offset += w->bounds().OffsetFromOrigin();
  return bounds() + offset;
}

void Window::SetClientArea(
    const gfx::Insets& client_area,
    const std::vector<gfx::Rect>& additional_client_areas) {
  if (!WasCreatedByThisClientOrIsRoot(this))
    return;

  if (client_)
    client_->SetClientArea(server_id_, client_area,
                           additional_client_areas);
  LocalSetClientArea(client_area, additional_client_areas);
}

void Window::SetHitTestMask(const gfx::Rect& mask) {
  if (!WasCreatedByThisClientOrIsRoot(this))
    return;

  if (hit_test_mask_ && *hit_test_mask_ == mask)
    return;

  if (client_)
    client_->SetHitTestMask(server_id_, mask);
  hit_test_mask_.reset(new gfx::Rect(mask));
}

void Window::ClearHitTestMask() {
  if (!WasCreatedByThisClientOrIsRoot(this))
    return;

  if (!hit_test_mask_)
    return;

  if (client_)
    client_->ClearHitTestMask(server_id_);
  hit_test_mask_.reset();
}

void Window::SetVisible(bool value) {
  if (visible_ == value)
    return;

  if (client_)
    client_->SetVisible(this, value);
  LocalSetVisible(value);
}

void Window::SetOpacity(float opacity) {
  if (client_)
    client_->SetOpacity(this, opacity);
  LocalSetOpacity(opacity);
}

void Window::SetPredefinedCursor(ui::mojom::Cursor cursor_id) {
  if (cursor_id_ == cursor_id)
    return;

  if (client_)
    client_->SetPredefinedCursor(server_id_, cursor_id);
  LocalSetPredefinedCursor(cursor_id);
}

bool Window::IsDrawn() const {
  if (!visible_)
    return false;
  return parent_ ? parent_->IsDrawn() : parent_drawn_;
}

std::unique_ptr<WindowCompositorFrameSink> Window::RequestCompositorFrameSink(
    mojom::CompositorFrameSinkType type,
    scoped_refptr<cc::ContextProvider> context_provider,
    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager) {
  std::unique_ptr<WindowCompositorFrameSinkBinding>
      compositor_frame_sink_binding;
  std::unique_ptr<WindowCompositorFrameSink> compositor_frame_sink =
      WindowCompositorFrameSink::Create(std::move(context_provider),
                                        gpu_memory_buffer_manager,
                                        &compositor_frame_sink_binding);
  AttachCompositorFrameSink(type, std::move(compositor_frame_sink_binding));
  return compositor_frame_sink;
}

void Window::AttachCompositorFrameSink(
    mojom::CompositorFrameSinkType type,
    std::unique_ptr<WindowCompositorFrameSinkBinding>
        compositor_frame_sink_binding) {
  window_tree()->AttachCompositorFrameSink(
      server_id_, type,
      std::move(compositor_frame_sink_binding->compositor_frame_sink_request_),
      mojo::MakeProxy(std::move(
          compositor_frame_sink_binding->compositor_frame_sink_client_)));
}

void Window::ClearSharedProperty(const std::string& name) {
  SetSharedPropertyInternal(name, nullptr);
}

bool Window::HasSharedProperty(const std::string& name) const {
  return properties_.count(name) > 0;
}

void Window::AddObserver(WindowObserver* observer) {
  observers_.AddObserver(observer);
}

void Window::RemoveObserver(WindowObserver* observer) {
  observers_.RemoveObserver(observer);
}

const Window* Window::GetRoot() const {
  const Window* root = this;
  for (const Window* parent = this; parent; parent = parent->parent())
    root = parent;
  return root;
}

void Window::AddChild(Window* child) {
  // TODO(beng): not necessarily valid to all clients, but possibly to the
  //             embeddee in an embedder-embeddee relationship.
  if (client_)
    CHECK_EQ(child->client_, client_);
  // Roots can not be added as children of other windows.
  if (window_tree() && window_tree()->IsRoot(child))
    return;
  LocalAddChild(child);
  if (client_)
    client_->AddChild(this, child->server_id());
}

void Window::RemoveChild(Window* child) {
  // TODO(beng): not necessarily valid to all clients, but possibly to the
  //             embeddee in an embedder-embeddee relationship.
  if (client_)
    CHECK_EQ(child->client_, client_);
  LocalRemoveChild(child);
  if (client_)
    client_->RemoveChild(this, child->server_id());
}

void Window::Reorder(Window* relative, mojom::OrderDirection direction) {
  if (!LocalReorder(relative, direction))
    return;
  if (client_)
    client_->Reorder(this, relative->server_id(), direction);
}

void Window::MoveToFront() {
  if (!parent_ || parent_->children_.back() == this)
    return;
  Reorder(parent_->children_.back(), mojom::OrderDirection::ABOVE);
}

void Window::MoveToBack() {
  if (!parent_ || parent_->children_.front() == this)
    return;
  Reorder(parent_->children_.front(), mojom::OrderDirection::BELOW);
}

bool Window::Contains(const Window* child) const {
  if (!child)
    return false;
  if (child == this)
    return true;
  if (client_)
    CHECK_EQ(child->client_, client_);
  for (const Window* p = child->parent(); p; p = p->parent()) {
    if (p == this)
      return true;
  }
  return false;
}

void Window::AddTransientWindow(Window* transient_window) {
  // A system modal window cannot become a transient child.
  DCHECK(!transient_window->is_modal() || transient_window->transient_parent());

  if (client_)
    CHECK_EQ(transient_window->client_, client_);
  LocalAddTransientWindow(transient_window);
  if (client_)
    client_->AddTransientWindow(this, transient_window->server_id());
}

void Window::RemoveTransientWindow(Window* transient_window) {
  if (client_)
    CHECK_EQ(transient_window->window_tree(), client_);
  LocalRemoveTransientWindow(transient_window);
  if (client_)
    client_->RemoveTransientWindowFromParent(transient_window);
}

void Window::SetModal() {
  if (is_modal_)
    return;

  LocalSetModal();
  if (client_)
    client_->SetModal(this);
}

Window* Window::GetChildByLocalId(int id) {
  if (id == local_id_)
    return this;
  // TODO(beng): this could be improved depending on how we decide to own
  // windows.
  for (Window* child : children_) {
    Window* matching_child = child->GetChildByLocalId(id);
    if (matching_child)
      return matching_child;
  }
  return nullptr;
}

void Window::SetTextInputState(mojo::TextInputStatePtr state) {
  if (client_)
    client_->SetWindowTextInputState(server_id_, std::move(state));
}

void Window::SetImeVisibility(bool visible, mojo::TextInputStatePtr state) {
  // SetImeVisibility() shouldn't be used if the window is not editable.
  DCHECK(state.is_null() || state->type != mojo::TextInputType::NONE);
  if (client_)
    client_->SetImeVisibility(server_id_, visible, std::move(state));
}

bool Window::HasCapture() const {
  return client_ && client_->GetCaptureWindow() == this;
}

void Window::SetCapture() {
  if (client_)
    client_->SetCapture(this);
}

void Window::ReleaseCapture() {
  if (client_)
    client_->ReleaseCapture(this);
}

void Window::SetFocus() {
  if (client_ && IsDrawn())
    client_->SetFocus(this);
}

bool Window::HasFocus() const {
  return client_ && client_->GetFocusedWindow() == this;
}

void Window::SetCanFocus(bool can_focus) {
  if (client_)
    client_->SetCanFocus(server_id_, can_focus);
}

void Window::SetCanAcceptDrops(WindowDropTarget* drop_target) {
  if (drop_target_ == drop_target)
    return;
  drop_target_ = drop_target;
  if (client_)
    client_->SetCanAcceptDrops(server_id_, !!drop_target_);
}

void Window::SetCanAcceptEvents(bool can_accept_events) {
  if (can_accept_events_ == can_accept_events)
    return;
  can_accept_events_ = can_accept_events;
  if (client_)
    client_->SetCanAcceptEvents(server_id_, can_accept_events_);
}

void Window::Embed(ui::mojom::WindowTreeClientPtr client, uint32_t flags) {
  Embed(std::move(client), base::Bind(&EmptyEmbedCallback), flags);
}

void Window::Embed(ui::mojom::WindowTreeClientPtr client,
                   const EmbedCallback& callback,
                   uint32_t flags) {
  if (PrepareForEmbed())
    client_->Embed(server_id_, std::move(client), flags, callback);
  else
    callback.Run(false);
}

void Window::RequestClose() {
  if (client_)
    client_->RequestClose(this);
}

void Window::PerformDragDrop(
    const std::map<std::string, std::vector<uint8_t>>& drag_data,
    int drag_operation,
    const gfx::Point& cursor_location,
    const SkBitmap& bitmap,
    const base::Callback<void(bool, uint32_t)>& callback) {
  client_->PerformDragDrop(this, drag_data, drag_operation, cursor_location,
                           bitmap, callback);
}

void Window::CancelDragDrop() {
  client_->CancelDragDrop(this);
}

void Window::PerformWindowMove(mojom::MoveLoopSource source,
                               const gfx::Point& cursor_location,
                               const base::Callback<void(bool)>& callback) {
  client_->PerformWindowMove(this, source, cursor_location, callback);
}

void Window::CancelWindowMove() {
  client_->CancelWindowMove(this);
}

std::string Window::GetName() const {
  if (HasSharedProperty(mojom::WindowManager::kName_Property))
    return GetSharedProperty<std::string>(mojom::WindowManager::kName_Property);

  return std::string();
}

////////////////////////////////////////////////////////////////////////////////
// Window, protected:

Window::Window() : Window(nullptr, static_cast<Id>(-1)) {}

Window::~Window() {
  for (auto& observer : observers_)
    observer.OnWindowDestroying(this);
  if (client_)
    client_->OnWindowDestroying(this);

  if (HasFocus()) {
    // The focused window is being removed. When this happens the server
    // advances focus. We don't want to randomly pick a Window to get focus, so
    // we update local state only, and wait for the next focus change from the
    // server.
    client_->LocalSetFocus(nullptr);
  }

  // Remove from transient parent.
  if (transient_parent_)
    transient_parent_->LocalRemoveTransientWindow(this);

  // Return the surface reference if there is one.
  if (surface_info_)
    LocalSetSurfaceId(nullptr);

  // Remove transient children.
  while (!transient_children_.empty()) {
    Window* transient_child = transient_children_.front();
    LocalRemoveTransientWindow(transient_child);
    transient_child->LocalDestroy();
    DCHECK(transient_children_.empty() ||
           transient_children_.front() != transient_child);
  }

  if (parent_)
    parent_->LocalRemoveChild(this);

  // We may still have children. This can happen if the embedder destroys the
  // root while we're still alive.
  while (!children_.empty()) {
    Window* child = children_.front();
    LocalRemoveChild(child);
    DCHECK(children_.empty() || children_.front() != child);
  }

  // Notify observers before clearing properties (order matches aura::Window).
  for (auto& observer : observers_)
    observer.OnWindowDestroyed(this);

  // Clear properties.
  for (auto& pair : prop_map_) {
    if (pair.second.deallocator)
      (*pair.second.deallocator)(pair.second.value);
  }
  prop_map_.clear();

  // Invoke after observers so that can clean up any internal state observers
  // may have changed.
  if (window_tree())
    window_tree()->OnWindowDestroyed(this);
}

////////////////////////////////////////////////////////////////////////////////
// Window, private:

Window::Window(WindowTreeClient* client, Id id)
    : client_(client),
      server_id_(id),
      parent_(nullptr),
      stacking_target_(nullptr),
      transient_parent_(nullptr),
      is_modal_(false),
      // Matches aura, see aura::Window for details.
      observers_(base::ObserverList<WindowObserver>::NOTIFY_EXISTING_ONLY),
      input_event_handler_(nullptr),
      surface_id_handler_(nullptr),
      visible_(false),
      opacity_(1.0f),
      display_id_(display::Display::kInvalidDisplayID),
      cursor_id_(mojom::Cursor::CURSOR_NULL),
      parent_drawn_(false) {}

void Window::SetSharedPropertyInternal(const std::string& name,
                                       const std::vector<uint8_t>* value) {
  if (!WasCreatedByThisClientOrIsRoot(this))
    return;

  if (client_) {
    mojo::Array<uint8_t> transport_value(nullptr);
    if (value) {
      transport_value.resize(value->size());
      if (value->size())
        memcpy(&transport_value.front(), &(value->front()), value->size());
    }
    // TODO: add test coverage of this (450303).
    client_->SetProperty(this, name, std::move(transport_value));
  }
  LocalSetSharedProperty(name, value);
}

int64_t Window::SetLocalPropertyInternal(const void* key,
                                         const char* name,
                                         PropertyDeallocator deallocator,
                                         int64_t value,
                                         int64_t default_value) {
  int64_t old = GetLocalPropertyInternal(key, default_value);
  if (value == default_value) {
    prop_map_.erase(key);
  } else {
    Value prop_value;
    prop_value.name = name;
    prop_value.value = value;
    prop_value.deallocator = deallocator;
    prop_map_[key] = prop_value;
  }
  for (auto& observer : observers_)
    observer.OnWindowLocalPropertyChanged(this, key, old);
  return old;
}

int64_t Window::GetLocalPropertyInternal(const void* key,
                                         int64_t default_value) const {
  std::map<const void*, Value>::const_iterator iter = prop_map_.find(key);
  if (iter == prop_map_.end())
    return default_value;
  return iter->second.value;
}

void Window::LocalDestroy() {
  delete this;
}

void Window::LocalAddChild(Window* child) {
  ScopedTreeNotifier notifier(child, child->parent(), this);
  if (child->parent())
    RemoveChildImpl(child, &child->parent_->children_);
  children_.push_back(child);
  child->parent_ = this;
  child->display_id_ = display_id_;
}

void Window::LocalRemoveChild(Window* child) {
  DCHECK_EQ(this, child->parent());
  ScopedTreeNotifier notifier(child, this, nullptr);
  RemoveChildImpl(child, &children_);
}

void Window::LocalAddTransientWindow(Window* transient_window) {
  if (transient_window->transient_parent())
    RemoveTransientWindowImpl(transient_window);
  transient_children_.push_back(transient_window);
  transient_window->transient_parent_ = this;

  // Restack |transient_window| properly above its transient parent, if they
  // share the same parent.
  if (transient_window->parent() == parent())
    RestackTransientDescendants(this, &GetStackingTarget,
                                &ReorderWithoutNotification);

  for (auto& observer : observers_)
    observer.OnTransientChildAdded(this, transient_window);
}

void Window::LocalRemoveTransientWindow(Window* transient_window) {
  DCHECK_EQ(this, transient_window->transient_parent());
  RemoveTransientWindowImpl(transient_window);
  for (auto& observer : observers_)
    observer.OnTransientChildRemoved(this, transient_window);
}

void Window::LocalSetModal() {
  is_modal_ = true;
}

bool Window::LocalReorder(Window* relative, mojom::OrderDirection direction) {
  OrderChangedNotifier notifier(this, relative, direction);
  return ReorderImpl(this, relative, direction, &notifier);
}

void Window::LocalSetBounds(const gfx::Rect& old_bounds,
                            const gfx::Rect& new_bounds) {
  // If this client owns the window, then it should be the only one to change
  // the bounds.
  DCHECK(!WasCreatedByThisClient() || old_bounds == bounds_);
  ScopedSetBoundsNotifier notifier(this, old_bounds, new_bounds);
  bounds_ = new_bounds;
}

void Window::LocalSetClientArea(
    const gfx::Insets& new_client_area,
    const std::vector<gfx::Rect>& additional_client_areas) {
  const std::vector<gfx::Rect> old_additional_client_areas =
      additional_client_areas_;
  const gfx::Insets old_client_area = client_area_;
  client_area_ = new_client_area;
  additional_client_areas_ = additional_client_areas;
  for (auto& observer : observers_) {
    observer.OnWindowClientAreaChanged(this, old_client_area,
                                       old_additional_client_areas);
  }
}

void Window::LocalSetDisplay(int64_t display_id) {
  display_id_ = display_id;
  // TODO(sad): Notify observers (of this window, and of the descendant windows)
  // when a window moves from one display into another. https://crbug.com/614887
}

void Window::LocalSetParentDrawn(bool value) {
  if (parent_drawn_ == value)
    return;

  // As IsDrawn() is derived from |visible_| and |parent_drawn_|, only send
  // drawn notification is the value of IsDrawn() is really changing.
  if (IsDrawn() == value) {
    parent_drawn_ = value;
    return;
  }
  for (auto& observer : observers_)
    observer.OnWindowDrawnChanging(this);
  parent_drawn_ = value;
  for (auto& observer : observers_)
    observer.OnWindowDrawnChanged(this);
}

void Window::LocalSetVisible(bool visible) {
  if (visible_ == visible)
    return;

  for (auto& observer : observers_)
    observer.OnWindowVisibilityChanging(this, visible);
  visible_ = visible;
  if (parent_) {
    for (auto& observer : parent_->observers_)
      observer.OnChildWindowVisibilityChanged(this, visible);
  }

  NotifyWindowVisibilityChanged(this, visible);
}

void Window::LocalSetOpacity(float opacity) {
  if (opacity_ == opacity)
    return;

  float old_opacity = opacity_;
  opacity_ = opacity;
  for (auto& observer : observers_)
    observer.OnWindowOpacityChanged(this, old_opacity, opacity_);
}

void Window::LocalSetPredefinedCursor(mojom::Cursor cursor_id) {
  if (cursor_id_ == cursor_id)
    return;

  cursor_id_ = cursor_id;
  for (auto& observer : observers_)
    observer.OnWindowPredefinedCursorChanged(this, cursor_id);
}

void Window::LocalSetSharedProperty(const std::string& name,
                                    const std::vector<uint8_t>* value) {
  std::vector<uint8_t> old_value;
  std::vector<uint8_t>* old_value_ptr = nullptr;
  auto it = properties_.find(name);
  if (it != properties_.end()) {
    old_value = it->second;
    old_value_ptr = &old_value;

    if (value && old_value == *value)
      return;
  } else if (!value) {
    // This property isn't set in |properties_| and |value| is nullptr, so
    // there's no change.
    return;
  }

  if (value) {
    properties_[name] = *value;
  } else if (it != properties_.end()) {
    properties_.erase(it);
  }

  for (auto& observer : observers_)
    observer.OnWindowSharedPropertyChanged(this, name, old_value_ptr, value);
}

void Window::LocalSetSurfaceId(std::unique_ptr<SurfaceInfo> surface_info) {
  if (surface_info_) {
    const cc::SurfaceId& existing_surface_id = surface_info_->surface_id;
    cc::SurfaceId new_surface_id =
        surface_info ? surface_info->surface_id : cc::SurfaceId();
    if (existing_surface_id.is_valid() &&
        existing_surface_id != new_surface_id) {
      // TODO(kylechar): Start return reference here?
    }
  }
  if (parent_ && parent_->surface_id_handler_) {
    parent_->surface_id_handler_->OnChildWindowSurfaceChanged(this,
                                                              &surface_info);
  }
  surface_info_ = std::move(surface_info);
}

void Window::NotifyWindowStackingChanged() {
  if (stacking_target_) {
    Children::const_iterator window_i = std::find(
        parent()->children().begin(), parent()->children().end(), this);
    DCHECK(window_i != parent()->children().end());
    if (window_i != parent()->children().begin() &&
        (*(window_i - 1) == stacking_target_))
      return;
  }
  RestackTransientDescendants(this, &GetStackingTarget,
                              &ReorderWithoutNotification);
}

void Window::NotifyWindowVisibilityChanged(Window* target, bool visible) {
  if (!NotifyWindowVisibilityChangedDown(target, visible))
    return;  // |this| has been deleted.

  NotifyWindowVisibilityChangedUp(target, visible);
}

bool Window::NotifyWindowVisibilityChangedAtReceiver(Window* target,
                                                     bool visible) {
  // |this| may be deleted during a call to OnWindowVisibilityChanged() on one
  // of the observers. We create an local observer for that. In that case we
  // exit without further access to any members.
  WindowTracker tracker;
  tracker.Add(this);
  for (auto& observer : observers_)
    observer.OnWindowVisibilityChanged(target, visible);
  return tracker.Contains(this);
}

bool Window::NotifyWindowVisibilityChangedDown(Window* target, bool visible) {
  if (!NotifyWindowVisibilityChangedAtReceiver(target, visible))
    return false;  // |this| was deleted.
  std::set<const Window*> child_already_processed;
  bool child_destroyed = false;
  do {
    child_destroyed = false;
    for (Window::Children::const_iterator it = children_.begin();
         it != children_.end(); ++it) {
      if (!child_already_processed.insert(*it).second)
        continue;
      if (!(*it)->NotifyWindowVisibilityChangedDown(target, visible)) {
        // |*it| was deleted, |it| is invalid and |children_| has changed.  We
        // exit the current for-loop and enter a new one.
        child_destroyed = true;
        break;
      }
    }
  } while (child_destroyed);
  return true;
}

void Window::NotifyWindowVisibilityChangedUp(Window* target, bool visible) {
  // Start with the parent as we already notified |this|
  // in NotifyWindowVisibilityChangedDown.
  for (Window* window = parent(); window; window = window->parent()) {
    bool ret = window->NotifyWindowVisibilityChangedAtReceiver(target, visible);
    DCHECK(ret);
  }
}

bool Window::PrepareForEmbed() {
  if (!WasCreatedByThisClient())
    return false;

  while (!children_.empty())
    RemoveChild(children_[0]);
  return true;
}

void Window::RemoveTransientWindowImpl(Window* transient_window) {
  Window::Children::iterator it = std::find(
      transient_children_.begin(), transient_children_.end(), transient_window);
  if (it != transient_children_.end()) {
    transient_children_.erase(it);
    transient_window->transient_parent_ = nullptr;
  }
  // If |transient_window| and its former transient parent share the same
  // parent, |transient_window| should be restacked properly so it is not among
  // transient children of its former parent, anymore.
  if (parent() == transient_window->parent())
    RestackTransientDescendants(this, &GetStackingTarget,
                                &ReorderWithoutNotification);

  // TOOD(fsamuel): We might want to notify observers here.
}

// static
void Window::ReorderWithoutNotification(Window* window,
                                        Window* relative,
                                        mojom::OrderDirection direction) {
  ReorderImpl(window, relative, direction, nullptr);
}

// static
bool Window::ReorderImpl(Window* window,
                         Window* relative,
                         mojom::OrderDirection direction,
                         OrderChangedNotifier* notifier) {
  DCHECK(relative);
  DCHECK_NE(window, relative);
  DCHECK_EQ(window->parent(), relative->parent());
  DCHECK(window->parent());

  if (!AdjustStackingForTransientWindows(&window, &relative, &direction,
                                         window->stacking_target_))
    return false;

  const size_t child_i = std::find(window->parent_->children_.begin(),
                                   window->parent_->children_.end(), window) -
                         window->parent_->children_.begin();
  const size_t target_i =
      std::find(window->parent_->children_.begin(),
                window->parent_->children_.end(), relative) -
      window->parent_->children_.begin();
  if ((direction == mojom::OrderDirection::ABOVE && child_i == target_i + 1) ||
      (direction == mojom::OrderDirection::BELOW && child_i + 1 == target_i)) {
    return false;
  }

  if (notifier)
    notifier->NotifyWindowReordering();

  const size_t dest_i = direction == mojom::OrderDirection::ABOVE
                            ? (child_i < target_i ? target_i : target_i + 1)
                            : (child_i < target_i ? target_i - 1 : target_i);
  window->parent_->children_.erase(window->parent_->children_.begin() +
                                   child_i);
  window->parent_->children_.insert(window->parent_->children_.begin() + dest_i,
                                    window);

  window->NotifyWindowStackingChanged();

  if (notifier)
    notifier->NotifyWindowReordered();

  return true;
}

// static
Window** Window::GetStackingTarget(Window* window) {
  return &window->stacking_target_;
}
}  // namespace ui
