| // 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 |