// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/controls/native/native_view_host.h"

#include <memory>

#include "base/feature_list.h"
#include "ui/aura/window.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/controls/native/native_view_host_test_base.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"

namespace views {

class NativeViewHostTest : public test::NativeViewHostTestBase {
 public:
  NativeViewHostTest() = default;

  NativeViewHostTest(const NativeViewHostTest&) = delete;
  NativeViewHostTest& operator=(const NativeViewHostTest&) = delete;

  void SetUp() override {
    ViewsTestBase::SetUp();
    CreateTopLevel();
  }
};

namespace {

// View implementation used by NativeViewHierarchyChanged to count number of
// times NativeViewHierarchyChanged() is invoked.
class NativeViewHierarchyChangedTestView : public View {
  METADATA_HEADER(NativeViewHierarchyChangedTestView, View)

 public:
  NativeViewHierarchyChangedTestView() = default;

  NativeViewHierarchyChangedTestView(
      const NativeViewHierarchyChangedTestView&) = delete;
  NativeViewHierarchyChangedTestView& operator=(
      const NativeViewHierarchyChangedTestView&) = delete;

  void ResetCount() { notification_count_ = 0; }

  int notification_count() const { return notification_count_; }

  // Overriden from View:
  void NativeViewHierarchyChanged() override {
    ++notification_count_;
    View::NativeViewHierarchyChanged();
  }

 private:
  int notification_count_ = 0;
};

BEGIN_METADATA(NativeViewHierarchyChangedTestView)
END_METADATA

aura::Window* GetNativeParent(aura::Window* window) {
  return window->parent();
}

class ViewHierarchyChangedTestHost : public NativeViewHost {
  METADATA_HEADER(ViewHierarchyChangedTestHost, NativeViewHost)

 public:
  ViewHierarchyChangedTestHost() = default;

  ViewHierarchyChangedTestHost(const ViewHierarchyChangedTestHost&) = delete;
  ViewHierarchyChangedTestHost& operator=(const ViewHierarchyChangedTestHost&) =
      delete;

  void ResetParentChanges() { num_parent_changes_ = 0; }

  int num_parent_changes() const { return num_parent_changes_; }

  // NativeViewHost:
  void ViewHierarchyChanged(
      const ViewHierarchyChangedDetails& details) override {
    gfx::NativeView parent_before =
        native_view() ? GetNativeParent(native_view()) : nullptr;
    NativeViewHost::ViewHierarchyChanged(details);
    gfx::NativeView parent_after =
        native_view() ? GetNativeParent(native_view()) : nullptr;
    if (parent_before != parent_after) {
      ++num_parent_changes_;
    }
  }

 private:
  int num_parent_changes_ = 0;
};

BEGIN_METADATA(ViewHierarchyChangedTestHost)
END_METADATA

}  // namespace

// Verifies NativeViewHierarchyChanged is sent.
TEST_F(NativeViewHostTest, NativeViewHierarchyChanged) {
  if (!base::FeatureList::IsEnabled(
          views::features::kNativeViewHostManagesLayers)) {
    GTEST_SKIP();
  }
  // Create a child widget.
  NativeViewHierarchyChangedTestView* test_view =
      new NativeViewHierarchyChangedTestView;
  NativeViewHost* host = new NativeViewHost;
  std::unique_ptr<Widget> child = CreateChildForHost(
      toplevel()->GetNativeView(), toplevel()->GetRootView(), test_view, host);
  if (!base::FeatureList::IsEnabled(
          views::features::kNativeViewHostManagesLayers)) {
    // Two notifications are generated from inserting the native view into the
    // clipping window and then inserting the clipping window into the root
    // window.
    EXPECT_EQ(2, test_view->notification_count());
  } else {
    EXPECT_EQ(0, test_view->notification_count());
  }
  test_view->ResetCount();

  // Detaching should send a NativeViewHierarchyChanged() notification and
  // change the parent.
  host->Detach();
  if (!base::FeatureList::IsEnabled(
          views::features::kNativeViewHostManagesLayers)) {
    // Two notifications are generated from removing the native view from the
    // clipping window and then reparenting it to the root window.
    EXPECT_EQ(2, test_view->notification_count());
  } else {
    EXPECT_EQ(1, test_view->notification_count());
  }
  EXPECT_NE(toplevel()->GetNativeView(),
            GetNativeParent(child->GetNativeView()));
  test_view->ResetCount();

  // Attaching should send a NativeViewHierarchyChanged() notification and
  // reset the parent.
  host->Attach(child->GetNativeView());
  if (!base::FeatureList::IsEnabled(
          views::features::kNativeViewHostManagesLayers)) {
    // There is a clipping window inserted above the native view that needs to
    // be accounted for when looking at the relationship between the native
    // views.
    EXPECT_EQ(2, test_view->notification_count());
    EXPECT_EQ(toplevel()->GetNativeView(),
              GetNativeParent(GetNativeParent(child->GetNativeView())));
  } else {
    EXPECT_EQ(1, test_view->notification_count());
    EXPECT_EQ(toplevel()->GetNativeView(),
              GetNativeParent(child->GetNativeView()));
  }
}

// Verifies ViewHierarchyChanged handles NativeViewHost remove, add and move
// (reparent) operations with correct parent changes.
// This exercises the non-recursive code paths in
// View::PropagateRemoveNotifications() and View::PropagateAddNotifications().
TEST_F(NativeViewHostTest, ViewHierarchyChangedForHost) {
  // Original tree:
  // toplevel
  // +-- host0 (NativeViewHost)
  //     +-- child0 (Widget, attached to host0)
  //     +-- test_host (ViewHierarchyChangedTestHost)
  //         +-- test_child (Widget, attached to test_host)
  // +-- host1 (NativeViewHost)
  //     +-- child1 (Widget, attached to host1)

  // Add two children widgets attached to a NativeViewHost, and a test
  // grandchild as child widget of host0.
  NativeViewHost* host0 = new NativeViewHost;
  std::unique_ptr<Widget> child0 = CreateChildForHost(
      toplevel()->GetNativeView(), toplevel()->GetRootView(), new View, host0);
  NativeViewHost* host1 = new NativeViewHost;
  std::unique_ptr<Widget> child1 = CreateChildForHost(
      toplevel()->GetNativeView(), toplevel()->GetRootView(), new View, host1);
  ViewHierarchyChangedTestHost* test_host = new ViewHierarchyChangedTestHost;
  std::unique_ptr<Widget> test_child =
      CreateChildForHost(host0->native_view(), host0, new View, test_host);

  // Remove test_host from host0, expect 1 parent change.
  test_host->ResetParentChanges();
  EXPECT_EQ(0, test_host->num_parent_changes());
  host0->RemoveChildView(test_host);
  EXPECT_EQ(1, test_host->num_parent_changes());

  // Add test_host back to host0, expect 1 parent change.
  test_host->ResetParentChanges();
  EXPECT_EQ(0, test_host->num_parent_changes());
  host0->AddChildViewRaw(test_host);
  EXPECT_EQ(1, test_host->num_parent_changes());

  // Reparent test_host to host1, expect no parent change because the old and
  // new parents, host0 and host1, belong to the same toplevel widget.
  test_host->ResetParentChanges();
  EXPECT_EQ(0, test_host->num_parent_changes());
  host1->AddChildViewRaw(test_host);
  EXPECT_EQ(0, test_host->num_parent_changes());

  // Reparent test_host to contents view of child0, expect 2 parent changes
  // because the old parent belongs to the toplevel widget whereas the new
  // parent belongs to the child0.
  test_host->ResetParentChanges();
  EXPECT_EQ(0, test_host->num_parent_changes());
  child0->GetContentsView()->AddChildViewRaw(test_host);
  EXPECT_EQ(2, test_host->num_parent_changes());
}

// Verifies ViewHierarchyChanged handles NativeViewHost's parent remove, add and
// move (reparent) operations with correct parent changes.
// This exercises the recursive code paths in
// View::PropagateRemoveNotifications() and View::PropagateAddNotifications().
TEST_F(NativeViewHostTest, ViewHierarchyChangedForHostParent) {
  // Original tree:
  // toplevel
  // +-- view0 (View)
  //     +-- host0 (NativeViewHierarchyChangedTestHost)
  //         +-- child0 (Widget, attached to host0)
  // +-- view1 (View)
  //     +-- host1 (NativeViewHierarchyChangedTestHost)
  //         +-- child1 (Widget, attached to host1)

  // Add two children views.
  View* view0 = new View;
  toplevel()->GetRootView()->AddChildViewRaw(view0);
  View* view1 = new View;
  toplevel()->GetRootView()->AddChildViewRaw(view1);

  // To each child view, add a child widget.
  ViewHierarchyChangedTestHost* host0 = new ViewHierarchyChangedTestHost;
  std::unique_ptr<Widget> child0 =
      CreateChildForHost(toplevel()->GetNativeView(), view0, new View, host0);
  ViewHierarchyChangedTestHost* host1 = new ViewHierarchyChangedTestHost;
  std::unique_ptr<Widget> child1 =
      CreateChildForHost(toplevel()->GetNativeView(), view1, new View, host1);

  // Remove view0 from top level, expect 1 parent change.
  host0->ResetParentChanges();
  EXPECT_EQ(0, host0->num_parent_changes());
  toplevel()->GetRootView()->RemoveChildView(view0);
  EXPECT_EQ(1, host0->num_parent_changes());

  // Add view0 back to top level, expect 1 parent change.
  host0->ResetParentChanges();
  EXPECT_EQ(0, host0->num_parent_changes());
  toplevel()->GetRootView()->AddChildViewRaw(view0);
  EXPECT_EQ(1, host0->num_parent_changes());

  // Reparent view0 to view1, expect no parent change because the old and new
  // parents of both view0 and view1 belong to the same toplevel widget.
  host0->ResetParentChanges();
  host1->ResetParentChanges();
  EXPECT_EQ(0, host0->num_parent_changes());
  EXPECT_EQ(0, host1->num_parent_changes());
  view1->AddChildViewRaw(view0);
  EXPECT_EQ(0, host0->num_parent_changes());
  EXPECT_EQ(0, host1->num_parent_changes());

  // Restore original view hierarchy by adding back view0 to top level.
  // Then, reparent view1 to contents view of child0.
  // Expect 2 parent changes because the old parent belongs to the toplevel
  // widget whereas the new parent belongs to the 1st child widget.
  toplevel()->GetRootView()->AddChildViewRaw(view0);
  host0->ResetParentChanges();
  host1->ResetParentChanges();
  EXPECT_EQ(0, host0->num_parent_changes());
  EXPECT_EQ(0, host1->num_parent_changes());
  child0->GetContentsView()->AddChildViewRaw(view1);
  EXPECT_EQ(0, host0->num_parent_changes());
  EXPECT_EQ(2, host1->num_parent_changes());
}

}  // namespace views
