blob: bb196c2551a4882b2933c1652948aed3f25bd082 [file] [log] [blame]
// 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