// Copyright 2017 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 "content/common/unique_name_helper.h"

#include <map>
#include <memory>
#include <vector>

#include "base/auto_reset.h"
#include "base/optional.h"
#include "base/strings/nullable_string16.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "content/common/page_state_serialization.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {
namespace {

// Requested names longer than this (that are unique) should be hashed.
constexpr size_t kMaxSize = 80;

class TestFrameAdapter : public UniqueNameHelper::FrameAdapter {
 public:
  // |virtual_index_in_parent| is the virtual index of this frame in the
  // parent's list of children, as unique name generation should see it. Note
  // that this may differ from the actual index of this adapter in
  // |parent_->children_|.
  explicit TestFrameAdapter(TestFrameAdapter* parent,
                            int virtual_index_in_parent,
                            const std::string& requested_name)
      : parent_(parent),
        virtual_index_in_parent_(virtual_index_in_parent),
        unique_name_helper_(this) {
    if (parent_)
      parent_->children_.push_back(this);
    unique_name_helper_.UpdateName(requested_name);
    CalculateLegacyName(requested_name);
  }

  ~TestFrameAdapter() override {
    if (parent_) {
      parent_->children_.erase(std::find(parent_->children_.begin(),
                                         parent_->children_.end(), this));
    }
  }

  bool IsMainFrame() const override { return !parent_; }

  bool IsCandidateUnique(base::StringPiece name) const override {
    auto* top = this;
    while (top->parent_)
      top = top->parent_;
    return top->CheckUniqueness(name);
  }

  int GetSiblingCount() const override { return virtual_index_in_parent_; }

  int GetChildCount() const override {
    ADD_FAILURE()
        << "GetChildCount() should not be triggered by unit test code!";
    return 0;
  }

  std::vector<base::StringPiece> CollectAncestorNames(
      BeginPoint begin_point,
      bool (*should_stop)(base::StringPiece)) const override {
    EXPECT_EQ(BeginPoint::kParentFrame, begin_point);
    std::vector<base::StringPiece> result;
    for (auto* adapter = parent_; adapter; adapter = adapter->parent_) {
      result.push_back(adapter->GetNameForCurrentMode());
      if (should_stop(result.back()))
        break;
    }
    return result;
  }

  std::vector<int> GetFramePosition(BeginPoint begin_point) const override {
    EXPECT_EQ(BeginPoint::kParentFrame, begin_point);
    std::vector<int> result;
    for (auto* adapter = this; adapter->parent_; adapter = adapter->parent_)
      result.push_back(adapter->virtual_index_in_parent_);
    return result;
  }

  // Returns the new style name with a max size limit.
  const std::string& GetUniqueName() const {
    return unique_name_helper_.value();
  }

  // Calculate and return the legacy style name with no max size limit.
  const std::string& GetLegacyName() const { return legacy_name_; }

  // Populate a tree of FrameState with legacy unique names. The order of
  // FrameState children is guaranteed to match the order of TestFrameAdapter
  // children.
  void PopulateLegacyFrameState(ExplodedFrameState* frame_state) const {
    frame_state->target = base::UTF8ToUTF16(GetLegacyName());
    frame_state->children.resize(children_.size());
    for (size_t i = 0; i < children_.size(); ++i)
      children_[i]->PopulateLegacyFrameState(&frame_state->children[i]);
  }

  // Recursively verify that FrameState and its children have matching unique
  // names to this TestFrameAdapter.
  void VerifyUpdatedFrameState(const ExplodedFrameState& frame_state) const {
    EXPECT_EQ(GetUniqueName(),
              base::UTF16ToUTF8(frame_state.target.value_or(base::string16())));

    ASSERT_EQ(children_.size(), frame_state.children.size());
    for (size_t i = 0; i < children_.size(); ++i) {
      children_[i]->VerifyUpdatedFrameState(frame_state.children[i]);
    }
  }

  void UpdateName(const std::string& new_name) {
    unique_name_helper_.UpdateName(new_name);
  }

  void Freeze() { unique_name_helper_.Freeze(); }

 private:
  // Global toggle for the style of name to generate. Used to ensure that test
  // code can consistently trigger the legacy generation path when needed.
  static bool generate_legacy_name_;

  const std::string& GetNameForCurrentMode() const {
    return generate_legacy_name_ ? GetLegacyName() : GetUniqueName();
  }

  void CalculateLegacyName(const std::string& requested_name) {
    // Manually skip the main frame so its legacy name is always empty: this
    // is needed in the test as that logic lives at a different layer in
    // UniqueNameHelper.
    if (!IsMainFrame()) {
      base::AutoReset<bool> enable_legacy_mode(&generate_legacy_name_, true);
      legacy_name_ =
          UniqueNameHelper::CalculateLegacyNameForTesting(this, requested_name);
    }
  }

  bool CheckUniqueness(base::StringPiece name) const {
    if (name == GetNameForCurrentMode())
      return false;
    for (auto* child : children_) {
      if (!child->CheckUniqueness(name))
        return false;
    }
    return true;
  }

  TestFrameAdapter* const parent_;
  std::vector<TestFrameAdapter*> children_;
  const int virtual_index_in_parent_;
  UniqueNameHelper unique_name_helper_;
  std::string legacy_name_;
};

bool TestFrameAdapter::generate_legacy_name_ = false;

// Test helper that verifies that legacy unique names in versions of PageState
// prior to 25 are correctly updated when deserialized.
void VerifyPageStateForTargetUpdate(const TestFrameAdapter& main_frame) {
  ExplodedPageState in_state;
  main_frame.PopulateLegacyFrameState(&in_state.top);

  // Version 24 is the last version with unlimited size unique names.
  std::string encoded_state;
  LegacyEncodePageStateForTesting(in_state, 24, &encoded_state);

  ExplodedPageState out_state;
  DecodePageState(encoded_state, &out_state);

  main_frame.VerifyUpdatedFrameState(out_state.top);
}

TEST(UniqueNameHelper, Basic) {
  // Main frames should always have an empty unique name.
  TestFrameAdapter main_frame(nullptr, -1, "my main frame");
  EXPECT_EQ("", main_frame.GetUniqueName());
  EXPECT_EQ("", main_frame.GetLegacyName());

  // A child frame with a requested name that is unique should use the requested
  // name.
  TestFrameAdapter frame_0(&main_frame, 0, "child frame with name");
  EXPECT_EQ("child frame with name", frame_0.GetUniqueName());
  EXPECT_EQ("child frame with name", frame_0.GetLegacyName());

  // A child frame with no requested name should receive a generated unique
  // name.
  TestFrameAdapter frame_7(&main_frame, 7, "");
  EXPECT_EQ("<!--framePath //<!--frame7-->-->", frame_7.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame7-->-->", frame_7.GetLegacyName());

  // Naming collision should force a fallback to using a generated unique name.
  TestFrameAdapter frame_2(&main_frame, 2, "child frame with name");
  EXPECT_EQ("<!--framePath //<!--frame2-->-->", frame_2.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->-->", frame_2.GetLegacyName());

  // Index collision should also force a fallback to using a generated unique
  // name.
  TestFrameAdapter frame_2a(&main_frame, 2, "");
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/0-->",
            frame_2a.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/0-->",
            frame_2a.GetLegacyName());

  // A child of a frame with a unique naming collision will incorporate the
  // frame position marker as part of its frame path, though it will look a bit
  // strange...
  TestFrameAdapter frame_2a_5(&frame_2a, 5, "");
  EXPECT_EQ(
      "<!--framePath //<!--frame2-->--><!--framePosition-2/0/<!--frame5-->-->",
      frame_2a_5.GetUniqueName());
  EXPECT_EQ(
      "<!--framePath //<!--frame2-->--><!--framePosition-2/0/<!--frame5-->-->",
      frame_2a_5.GetLegacyName());

  // Index and name collision should also force a fallback to using a generated
  // unique name.
  TestFrameAdapter frame_2b(&main_frame, 2, "child frame with name");
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/1-->",
            frame_2b.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/1-->",
            frame_2b.GetLegacyName());

  VerifyPageStateForTargetUpdate(main_frame);
}

TEST(UniqueNameHelper, Hashing) {
  // Main frames should always have an empty unique name.
  TestFrameAdapter main_frame(nullptr, -1, "my main frame");
  EXPECT_EQ("", main_frame.GetUniqueName());
  EXPECT_EQ("", main_frame.GetLegacyName());

  // A child frame with a requested name that is unique but too long should fall
  // back to hashing.
  const std::string too_long_name(kMaxSize + 1, 'a');
  TestFrameAdapter frame_0(&main_frame, 0, too_long_name);
  EXPECT_EQ(
      "<!--"
      "frameHash8C48280D57FB88F161ADF34D9F597D93CA32B7EDFCD23B2AFE64C3789B3F785"
      "5-->",
      frame_0.GetUniqueName());
  EXPECT_EQ(too_long_name, frame_0.GetLegacyName());

  // A child frame with no requested name should receive a generated unique
  // name.
  TestFrameAdapter frame_7(&main_frame, 7, "");
  EXPECT_EQ("<!--framePath //<!--frame7-->-->", frame_7.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame7-->-->", frame_7.GetLegacyName());

  // Verify that a requested name that's over the limit collides with the hashed
  // version of its requested name.
  TestFrameAdapter frame_2(&main_frame, 2, too_long_name);
  EXPECT_EQ("<!--framePath //<!--frame2-->-->", frame_2.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->-->", frame_2.GetLegacyName());

  // Index collision should also force a fallback to using a generated unique
  // name.
  TestFrameAdapter frame_2a(&main_frame, 2, "");
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/0-->",
            frame_2a.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/0-->",
            frame_2a.GetLegacyName());

  // A child of a frame with a unique naming collision will incorporate the
  // frame position marker as part of its frame path, though it will look a bit
  // strange...
  TestFrameAdapter frame_2a_5(&frame_2a, 5, "");
  EXPECT_EQ(
      "<!--framePath //<!--frame2-->--><!--framePosition-2/0/<!--frame5-->-->",
      frame_2a_5.GetUniqueName());
  EXPECT_EQ(
      "<!--framePath //<!--frame2-->--><!--framePosition-2/0/<!--frame5-->-->",
      frame_2a_5.GetLegacyName());

  // Index and name collision should also force a fallback to using a generated
  // unique name.
  TestFrameAdapter frame_2b(&main_frame, 2, too_long_name);
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/1-->",
            frame_2b.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->--><!--framePosition-2/1-->",
            frame_2b.GetLegacyName());

  VerifyPageStateForTargetUpdate(main_frame);
}

// Verify that basic frame path generation always includes the full path from
// the root.
TEST(UniqueNameHelper, BasicGeneratedFramePath) {
  TestFrameAdapter main_frame(nullptr, -1, "my main frame");
  EXPECT_EQ("", main_frame.GetUniqueName());
  EXPECT_EQ("", main_frame.GetLegacyName());

  TestFrameAdapter frame_2(&main_frame, 2, "");
  EXPECT_EQ("<!--framePath //<!--frame2-->-->", frame_2.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->-->", frame_2.GetLegacyName());

  TestFrameAdapter frame_2_3(&frame_2, 3, "named grandchild");
  EXPECT_EQ("named grandchild", frame_2_3.GetUniqueName());
  EXPECT_EQ("named grandchild", frame_2_3.GetLegacyName());

  // Even though the parent frame has a unique name, the frame path should
  // include the full path from the root.
  TestFrameAdapter frame_2_3_5(&frame_2_3, 5, "");
  EXPECT_EQ("<!--framePath //<!--frame2-->/named grandchild/<!--frame5-->-->",
            frame_2_3_5.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame2-->/named grandchild/<!--frame5-->-->",
            frame_2_3_5.GetLegacyName());

  VerifyPageStateForTargetUpdate(main_frame);
}

TEST(UniqueNameHelper, GeneratedFramePathHashing) {
  TestFrameAdapter main_frame(nullptr, -1, "my main frame");
  EXPECT_EQ("", main_frame.GetUniqueName());
  EXPECT_EQ("", main_frame.GetLegacyName());

  TestFrameAdapter frame_0(&main_frame, 0, "");
  EXPECT_EQ("<!--framePath //<!--frame0-->-->", frame_0.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame0-->-->", frame_0.GetLegacyName());

  // At the limit, so the hashing fallback should not be triggered.
  const std::string just_fits_name(kMaxSize, 'a');
  TestFrameAdapter frame_0_0(&frame_0, 0, just_fits_name);
  EXPECT_EQ(just_fits_name, frame_0_0.GetUniqueName());
  EXPECT_EQ(just_fits_name, frame_0_0.GetLegacyName());

  // But anything over should trigger hashing.
  const std::string too_long_name(kMaxSize + 1, 'a');
  TestFrameAdapter frame_0_1(&frame_0, 1, too_long_name);
  EXPECT_EQ(
      "<!--"
      "frameHash8C48280D57FB88F161ADF34D9F597D93CA32B7EDFCD23B2AFE64C3789B3F785"
      "5-->",
      frame_0_1.GetUniqueName());
  EXPECT_EQ(too_long_name, frame_0_1.GetLegacyName());

  // A child frame should incorporate the parent's hashed requested name into
  // its frame path.
  TestFrameAdapter frame_0_1_0(&frame_0_1, 0, "");
  EXPECT_EQ(
      "<!--framePath "
      "//<!--frame0-->/"
      "<!--"
      "frameHash8C48280D57FB88F161ADF34D9F597D93CA32B7EDFCD23B2AFE64C3789B3F785"
      "5-->/<!--frame0-->-->",
      frame_0_1_0.GetUniqueName());
  EXPECT_EQ(
      "<!--framePath "
      "//<!--frame0-->/" +
          too_long_name + "/<!--frame0-->-->",
      frame_0_1_0.GetLegacyName());

  // Make sure that name replacement during legacy name updates don't
  // accidentally match on substrings: the name here is intentionally chosen so
  // that too_long_name is a substring.
  const std::string too_long_name2(kMaxSize + 10, 'a');
  TestFrameAdapter frame_0_2(&frame_0, 2, too_long_name2);
  EXPECT_EQ(
      "<!--"
      "frameHash6B2EC79170F50EA57B886DC81A2CF78721C651A002C8365A524019A7ED5A8A4"
      "0-->",
      frame_0_2.GetUniqueName());
  EXPECT_EQ(too_long_name2, frame_0_2.GetLegacyName());

  // Make sure that legacy name updates correctly handle multiple replacements.
  // An unnamed frame is used as the deepest descendant to ensure the requested
  // names from ancestors appear in the frame path. Begin with a named
  // grandparent:
  const std::string too_long_name3(kMaxSize * 2, 'b');
  TestFrameAdapter frame_0_1_1(&frame_0_1, 1, too_long_name3);
  EXPECT_EQ(
      "<!--"
      "frameHash3A0B065A4255F95EF6E206B11004B8805FB631A68F468A72CE26F7592C88C27"
      "A-->",
      frame_0_1_1.GetUniqueName());
  EXPECT_EQ(too_long_name3, frame_0_1_1.GetLegacyName());

  // And a named parent:
  const std::string too_long_name4(kMaxSize * 3, 'c');
  TestFrameAdapter frame_0_1_1_0(&frame_0_1_1, 0, too_long_name4);
  EXPECT_EQ(
      "<!--"
      "frameHashE00D028A784E645656638F4D461B81E779E5225CA9824C8E09664956CF4DAE3"
      "1-->",
      frame_0_1_1_0.GetUniqueName());
  EXPECT_EQ(too_long_name4, frame_0_1_1_0.GetLegacyName());

  // And finally an unnamed child to trigger fallback to the frame path:
  TestFrameAdapter frame_0_1_1_0_0(&frame_0_1_1_0, 0, "");
  EXPECT_EQ(
      "<!--framePath "
      "//<!--frame0-->/"
      "<!--"
      "frameHash8C48280D57FB88F161ADF34D9F597D93CA32B7EDFCD23B2AFE64C3789B3F785"
      "5-->/"
      "<!--"
      "frameHash3A0B065A4255F95EF6E206B11004B8805FB631A68F468A72CE26F7592C88C27"
      "A-->/"
      "<!--"
      "frameHashE00D028A784E645656638F4D461B81E779E5225CA9824C8E09664956CF4DAE3"
      "1-->/<!--frame0-->-->",
      frame_0_1_1_0_0.GetUniqueName());
  EXPECT_EQ("<!--framePath //<!--frame0-->/" + too_long_name + "/" +
                too_long_name3 + "/" + too_long_name4 + "/<!--frame0-->-->",
            frame_0_1_1_0_0.GetLegacyName());

  VerifyPageStateForTargetUpdate(main_frame);
}

TEST(UniqueNameHelper, UpdateName) {
  TestFrameAdapter main_frame(nullptr, -1, "my main frame");
  EXPECT_EQ("", main_frame.GetUniqueName());

  TestFrameAdapter frame_0(&main_frame, 0, "name1");
  EXPECT_EQ("name1", frame_0.GetUniqueName());

  frame_0.UpdateName("name2");
  EXPECT_EQ("name2", frame_0.GetUniqueName());

  frame_0.Freeze();
  frame_0.UpdateName("name3");
  EXPECT_EQ("name2", frame_0.GetUniqueName());  // No change expected.
}

}  // namespace
}  // namespace content
