| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/autofill/content/browser/content_autofill_driver.h" |
| #include "components/autofill/content/browser/content_autofill_driver_factory.h" |
| #include "components/autofill/content/browser/form_forest.h" |
| #include "components/autofill/content/browser/form_forest_test_api.h" |
| #include "components/autofill/content/browser/form_forest_util_inl.h" |
| #include "components/autofill/content/browser/test_autofill_client_injector.h" |
| #include "components/autofill/content/browser/test_autofill_driver_injector.h" |
| #include "components/autofill/content/browser/test_content_autofill_client.h" |
| #include "components/autofill/core/browser/autofill_test_utils.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h" |
| #include "third_party/blink/public/common/permissions_policy/permissions_policy.h" |
| #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-shared.h" |
| |
| using FrameData = autofill::internal::FormForest::FrameData; |
| using FrameDataSet = |
| base::flat_set<std::unique_ptr<FrameData>, FrameData::CompareByFrameToken>; |
| |
| using ::testing::AllOf; |
| using ::testing::ByRef; |
| using ::testing::ElementsAre; |
| using ::testing::ElementsAreArray; |
| using ::testing::Eq; |
| using ::testing::Field; |
| using ::testing::Matcher; |
| using ::testing::Pointee; |
| using ::testing::Property; |
| using ::testing::UnorderedElementsAreArray; |
| |
| namespace autofill::internal { |
| namespace { |
| |
| // Matchers. |
| |
| auto Equals(const FormFieldData& exp); |
| auto Equals(const FormData& exp); |
| auto Equals(const FrameData& exp); |
| |
| template <typename T> |
| auto ArrayEquals(const std::vector<T>& exp) { |
| std::vector<Matcher<T>> matchers; |
| for (const T& f : exp) |
| matchers.push_back(Equals(f)); |
| return ElementsAreArray(matchers); |
| } |
| |
| template <typename T> |
| auto UnorderedArrayEquals(const std::vector<T>& exp) { |
| std::vector<Matcher<T>> matchers; |
| for (const T& f : exp) |
| matchers.push_back(Equals(f)); |
| return UnorderedElementsAreArray(matchers); |
| } |
| |
| // The relevant attributes are FormFieldData::global_id(), FormFieldData::value. |
| // We additionally compare a few more attributes just for safety. |
| auto Equals(const FormFieldData& exp) { |
| return AllOf( |
| Property("global_id", &FormFieldData::global_id, exp.global_id()), |
| Field("name", &FormFieldData::name, exp.name), |
| Field("host_form_id", &FormFieldData::host_form_id, exp.host_form_id), |
| Field("origin", &FormFieldData::origin, exp.origin), |
| Field("form_control_type", &FormFieldData::form_control_type, |
| exp.form_control_type), |
| Field("value", &FormFieldData::value, exp.value), |
| Field("label", &FormFieldData::label, exp.label), |
| Field("host_form_signature", &FormFieldData::host_form_signature, |
| exp.host_form_signature)); |
| } |
| |
| // The relevant attributes are FormData::global_id(), FormData::fields. |
| // We additionally compare a few more attributes just for safety. |
| auto Equals(const FormData& exp) { |
| return AllOf(Property("global_id", &FormData::global_id, Eq(exp.global_id())), |
| Field("name", &FormData::name, exp.name), |
| Field("main_frame_origin", &FormData::main_frame_origin, |
| exp.main_frame_origin), |
| Field("action", &FormData::action, exp.action), |
| Field("full_url", &FormData::full_url, exp.full_url), |
| Field("url", &FormData::url, exp.url), |
| Field("fields", &FormData::fields, ArrayEquals(exp.fields))); |
| } |
| |
| // Compares all attributes of FrameData. |
| auto Equals(const FrameData& exp) { |
| return AllOf(Field("frame_token", &FrameData::frame_token, exp.frame_token), |
| Field("child_forms", &FrameData::child_forms, |
| ArrayEquals(exp.child_forms)), |
| Field("parent_form", &FrameData::parent_form, exp.parent_form), |
| Field("driver", &FrameData::driver, exp.driver)); |
| } |
| |
| // Deep comparison of the unique_ptrs in a FrameDataSet. |
| auto Equals(const FrameDataSet& exp) { |
| std::vector<Matcher<std::unique_ptr<FrameData>>> matchers; |
| for (const std::unique_ptr<FrameData>& x : exp) |
| matchers.push_back(Pointee(Equals(*x))); |
| return ElementsAreArray(matchers); |
| } |
| |
| // Compares all attributes of FormForest. (Since frame_datas_ is private, we use |
| // its accessor.) |
| auto Equals(const FormForest& exp) { |
| return Property(&FormForest::frame_datas, Equals(exp.frame_datas())); |
| } |
| |
| // Test form. |
| |
| // The basic test form is a credit card form with six fields: first name, last |
| // name, number, month, year, CVC. |
| FormData CreateForm() { |
| FormData form; |
| test::CreateTestCreditCardFormData(&form, true, false, true); |
| CHECK_EQ(form.fields.size(), 6u); |
| return form; |
| } |
| |
| // Creates a field type map for the form with N >= 0 repetitions of the fields |
| // from CreateForm(). |
| auto CreateFieldTypeMap(const FormData& form) { |
| CHECK_EQ(form.fields.size() % 6, 0u); |
| CHECK_GT(form.fields.size() / 6, 0u); |
| base::flat_map<FieldGlobalId, ServerFieldType> map; |
| for (size_t i = 0; i < form.fields.size() / 6; ++i) { |
| map[form.fields[6 * i + 0].global_id()] = CREDIT_CARD_NAME_FIRST; |
| map[form.fields[6 * i + 1].global_id()] = CREDIT_CARD_NAME_LAST; |
| map[form.fields[6 * i + 2].global_id()] = CREDIT_CARD_NUMBER; |
| map[form.fields[6 * i + 3].global_id()] = CREDIT_CARD_EXP_MONTH; |
| map[form.fields[6 * i + 4].global_id()] = CREDIT_CARD_EXP_4_DIGIT_YEAR; |
| map[form.fields[6 * i + 5].global_id()] = CREDIT_CARD_VERIFICATION_CODE; |
| } |
| return map; |
| } |
| |
| // A profile is a 6-bit integer, whose bits indicate different values of first |
| // and last name, credit card number, expiration month, expiration year, CVC. |
| using Profile = base::StrongAlias<struct ProfileTag, size_t>; |
| using ::autofill::test::WithoutValues; |
| |
| // Fills the fields 0..5 of |form| with data according to |profile|, the |
| // fields 6..11 with |profile|+1, etc. |
| FormData WithValues(FormData& form, Profile profile = Profile(0)) { |
| CHECK_EQ(form.fields.size() % 6, 0u); |
| CHECK_GT(form.fields.size() / 6, 0u); |
| for (size_t i = 0; i < form.fields.size() / 6; ++i) { |
| std::bitset<6> bitset(profile.value() + i); |
| form.fields[6 * i + 0].value = bitset.test(0) ? u"Jane" : u"John"; |
| form.fields[6 * i + 1].value = bitset.test(1) ? u"Doe" : u"Average"; |
| form.fields[6 * i + 2].value = |
| bitset.test(2) ? u"4444333322221111" : u"4444444444444444"; |
| form.fields[6 * i + 3].value = bitset.test(3) ? u"01" : u"12"; |
| form.fields[6 * i + 4].value = bitset.test(4) ? u"2083" : u"2087"; |
| form.fields[6 * i + 5].value = bitset.test(5) ? u"123" : u"456"; |
| } |
| return form; |
| } |
| |
| // Utility functions and constants. |
| |
| // Use strings for non-opaque origins and URLs because constructors must not be |
| // called before the test is set up. |
| const std::string kMainUrl("https://main.frame.com/"); |
| const std::string kIframeUrl("https://iframe.frame.com/"); |
| const std::string kOtherUrl("https://other.frame.com/"); |
| const url::Origin kOpaqueOrigin; |
| |
| url::Origin Origin(const GURL& url) { |
| return url::Origin::Create(url); |
| } |
| |
| url::Origin Origin(base::StringPiece url) { |
| return Origin(GURL(url)); |
| } |
| |
| LocalFrameToken Token(content::RenderFrameHost* rfh) { |
| return LocalFrameToken(rfh->GetFrameToken().value()); |
| } |
| |
| // Mimics ContentAutofillDriver::SetFormAndFrameMetaData(). |
| void SetMetaData(FormRendererId host_form, |
| FormFieldData& field, |
| content::RenderFrameHost* rfh) { |
| field.host_frame = Token(rfh); |
| field.host_form_id = host_form; |
| field.origin = rfh->GetLastCommittedOrigin(); |
| } |
| |
| void SetMetaData(FormData& form, content::RenderFrameHost* rfh) { |
| form.host_frame = Token(rfh); |
| form.main_frame_origin = rfh->GetMainFrame()->GetLastCommittedOrigin(); |
| for (FormFieldData& field : form.fields) |
| SetMetaData(form.unique_renderer_id, field, rfh); |
| } |
| |
| FormForestTestApi TestApi(FormForest& ff) { |
| return FormForestTestApi(&ff); |
| } |
| |
| FrameDataSet& frame_datas(FormForest& ff) { |
| return TestApi(ff).frame_datas(); |
| } |
| |
| // Flattens a vector by concatenating the elements of the outer vector. |
| template <typename T> |
| std::vector<T> Flattened(const std::vector<std::vector<T>>& xs) { |
| std::vector<T> concat; |
| for (const auto& x : xs) |
| concat.insert(concat.end(), x.begin(), x.end()); |
| return concat; |
| } |
| |
| // Computes all permutations of |xs|. |
| // Intended for testing::ValuesIn(). |
| template <typename T> |
| std::vector<std::vector<T>> Permutations(const std::vector<T>& xs) { |
| auto factorial = [](size_t n) -> size_t { |
| return std::round(std::tgamma(n + 1)); |
| }; |
| std::vector<std::vector<T>> ps; |
| ps.reserve(factorial(xs.size())); |
| ps.push_back(xs); |
| base::ranges::sort(ps.front()); |
| while (base::ranges::next_permutation(ps.front())) |
| ps.push_back(ps.front()); |
| CHECK_EQ(ps.size(), factorial(xs.size())); |
| return ps; |
| } |
| |
| // Computes the permutations of |xs| and concatenates each permutation. |
| // For example, |
| // FlattenedPermutations({{"a", "b"}, {"x", "y"}}) |
| // returns |
| // { {"a", "b", "x", "y"}, |
| // {"x", "y", "a", "b"} } |
| // because |
| // Permutations({{"a", "b"}, {"x", "y"}}) |
| // returns |
| // { {{"a", "b"}, {"x", "y"}}, |
| // {{"x", "y"}, {"a", "b"}} } |
| // and |
| // Flatten({{"a", "b"}, {"x", "y"}}) |
| // returns |
| // {"a", "b", "x", "y"}. |
| // Intended for testing::ValuesIn(), in particular for |
| // FormForestTestUpdateOrder. |
| template <typename T> |
| std::vector<std::vector<T>> FlattenedPermutations( |
| const std::vector<std::vector<T>>& xs) { |
| std::vector<std::vector<T>> result; |
| base::ranges::transform(Permutations(xs), std::back_inserter(result), |
| &Flattened<std::string>); |
| return result; |
| } |
| |
| class MockContentAutofillDriver : public ContentAutofillDriver { |
| public: |
| using ContentAutofillDriver::ContentAutofillDriver; |
| |
| LocalFrameToken token() { return Token(render_frame_host()); } |
| |
| // Fake whether a subframe is a root frame from the perspective of |
| // MockFlattening(). In the real world, this can happen, for example, because |
| // the frame's parent has not been seen yet, or because the frame itself |
| // became invisible and hence got cut off by its parent. |
| void set_sub_root(bool b) { is_sub_root_ = b; } |
| bool is_sub_root() const { return is_sub_root_; } |
| |
| MOCK_METHOD(void, TriggerFormExtraction, (), (override)); |
| |
| private: |
| bool is_sub_root_ = false; |
| }; |
| |
| // Fundamental test fixtures. |
| |
| // Test fixture for all FormForest tests. |
| // |
| // Within FormForestTest, we use RemoteFrameTokens only to test scenarios where |
| // the token cannot be resolved to a LocalFrameToken. In any other case, frames |
| // shall have LocalFrameTokens. This simplifies the mocking machinery needed |
| // (that is to say, I couldn't figure out how to mock frames with |
| // RemoteFrameTokens.) |
| class FormForestTest : public content::RenderViewHostTestHarness { |
| public: |
| // "Shared-autofill" may be enabled or disabled per frame for certain origins. |
| // The enum constants correspond to the following permission policies: |
| // - kDefault is the default policy, which enables shared-autofill on the |
| // main frame origin. |
| // - kSharedAutofill explicitly enables shared-autofill on a (child-) frame |
| // for its current origin. |
| // - kNoSharedAutofill explicitly disables shared-autofill on a frame for all |
| // origins. |
| // Child frames inherit the policy from their parents. |
| // "Shared-autofill" restricts cross-origin filling (see |
| // FormForest::GetBrowserForm() for details). |
| enum class Policy { kDefault, kSharedAutofill, kNoSharedAutofill }; |
| |
| void SetUp() override { |
| RenderViewHostTestHarness::SetUp(); |
| CHECK(kOpaqueOrigin.opaque()); |
| } |
| |
| void TearDown() override { RenderViewHostTestHarness::TearDown(); } |
| |
| protected: |
| MockContentAutofillDriver* NavigateMainFrame( |
| const GURL& url, |
| Policy policy = Policy::kDefault) { |
| auto simulator = content::NavigationSimulator::CreateBrowserInitiated( |
| url, web_contents()); |
| switch (policy) { |
| case Policy::kDefault: |
| break; |
| case Policy::kSharedAutofill: |
| simulator->SetPermissionsPolicyHeader(AllowSharedAutofill(Origin(url))); |
| break; |
| case Policy::kNoSharedAutofill: |
| simulator->SetPermissionsPolicyHeader(DisallowSharedAutofill()); |
| break; |
| } |
| simulator->Commit(); |
| return autofill_driver_injector_[main_rfh()]; |
| } |
| |
| // Creates a fresh child frame of |parent| with permissions |policy| and |
| // navigates it to |url|. The frame's name appears to be optional. |
| MockContentAutofillDriver* CreateAndNavigateChildFrame( |
| ContentAutofillDriver* parent, |
| const GURL& url, |
| Policy policy, |
| base::StringPiece name) { |
| blink::ParsedPermissionsPolicy declared_policy; |
| switch (policy) { |
| case Policy::kDefault: |
| declared_policy = {}; |
| break; |
| case Policy::kSharedAutofill: |
| declared_policy = AllowSharedAutofill(Origin(url)); |
| break; |
| case Policy::kNoSharedAutofill: |
| declared_policy = DisallowSharedAutofill(); |
| break; |
| } |
| content::RenderFrameHost* rfh = |
| content::RenderFrameHostTester::For(parent->render_frame_host()) |
| ->AppendChildWithPolicy(static_cast<std::string>(name), |
| declared_policy); |
| // ContentAutofillDriverFactory::DidFinishNavigation() creates a driver for |
| // subframes only if |
| // `NavigationHandle::HasSubframeNavigationEntryCommitted()` is true. This |
| // is not the case for the first navigation. (In non-unit-tests, the first |
| // navigation creates a driver in |
| // ContentAutofillDriverFactory::BindAutofillDriver().) Therefore, |
| // we simulate *two* navigations here, and explicitly set the transition |
| // type for the second navigation. |
| std::unique_ptr<content::NavigationSimulator> simulator; |
| // First navigation: `HasSubframeNavigationEntryCommitted() == false`. |
| // Must be a different URL from the second navigation. |
| GURL about_blank("about:blank"); |
| CHECK_NE(about_blank, url); |
| simulator = |
| content::NavigationSimulator::CreateRendererInitiated(about_blank, rfh); |
| simulator->Commit(); |
| rfh = simulator->GetFinalRenderFrameHost(); |
| // Second navigation: `HasSubframeNavigationEntryCommitted() == true`. |
| // Must set the transition type to ui::PAGE_TRANSITION_MANUAL_SUBFRAME. |
| simulator = content::NavigationSimulator::CreateRendererInitiated(url, rfh); |
| simulator->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME); |
| simulator->Commit(); |
| rfh = simulator->GetFinalRenderFrameHost(); |
| return autofill_driver_injector_[rfh]; |
| } |
| |
| private: |
| // Explicitly allows shared-autofill on |origin|. |
| static blink::ParsedPermissionsPolicy AllowSharedAutofill( |
| url::Origin origin) { |
| return {blink::ParsedPermissionsPolicyDeclaration( |
| blink::mojom::PermissionsPolicyFeature::kSharedAutofill, |
| {*blink::OriginWithPossibleWildcards::FromOrigin(origin)}, |
| /*self_if_matches=*/absl::nullopt, |
| /*matches_all_origins=*/false, |
| /*matches_opaque_src=*/false)}; |
| } |
| |
| // Explicitly disallows shared-autofill on all origins. |
| static blink::ParsedPermissionsPolicy DisallowSharedAutofill() { |
| return {blink::ParsedPermissionsPolicyDeclaration( |
| blink::mojom::PermissionsPolicyFeature::kSharedAutofill, |
| /*allowed_origins=*/{}, /*self_if_matches=*/absl::nullopt, |
| /*matches_all_origins=*/false, |
| /*matches_opaque_src=*/false)}; |
| } |
| |
| base::test::ScopedFeatureList feature_list_{ |
| features::kAutofillSharedAutofill}; |
| test::AutofillUnitTestEnvironment autofill_test_environment_; |
| TestAutofillClientInjector<TestContentAutofillClient> |
| autofill_client_injector_; |
| TestAutofillDriverInjector<MockContentAutofillDriver> |
| autofill_driver_injector_; |
| }; |
| |
| // Test fixture with a mocked frame/form tree. |
| // |
| // FrameInfo and FormInfo represent mocked forms and can be syntactically |
| // arranged in a tree structure using designated initializers. |
| class FormForestTestWithMockedTree : public FormForestTest { |
| public: |
| struct FormInfo; |
| |
| struct FrameInfo { |
| std::string name = ""; |
| // The default value of |url| is changed to kMainUrl or kIframeUrl in |
| // MockFormForest(). |
| std::string url = ""; |
| std::vector<FormInfo> forms = {}; |
| FormForestTest::Policy policy = FormForestTest::Policy::kDefault; |
| // The index of the last field from the parent form that precedes this |
| // frame. This is analogous to FormData::child_frames[i].predecessor. |
| int field_predecessor = std::numeric_limits<int>::max(); |
| }; |
| |
| struct FormInfo { |
| std::string name = ""; |
| FormData form = CreateForm(); |
| std::vector<FrameInfo> frames = {}; |
| }; |
| |
| struct FormSpan { |
| std::string form; |
| size_t begin = 0; |
| size_t count = base::dynamic_extent; |
| }; |
| |
| void TearDown() override { |
| TestApi(mocked_forms_).Reset(); |
| TestApi(flattened_forms_).Reset(); |
| drivers_.clear(); |
| forms_.clear(); |
| FormForestTest::TearDown(); |
| } |
| |
| // Initializes the |mocked_forms_| according to the frame/form tree |
| // |frame_info|. |
| MockContentAutofillDriver* MockFormForest( |
| const FrameInfo& frame_info, |
| MockContentAutofillDriver* parent_driver = nullptr, |
| FormData* parent_form = nullptr) { |
| CHECK_EQ(!parent_driver, !parent_form); |
| GURL url(!frame_info.url.empty() |
| ? frame_info.url |
| : (!parent_driver ? kMainUrl : kIframeUrl)); |
| MockContentAutofillDriver* driver = |
| !parent_driver |
| ? NavigateMainFrame(url, frame_info.policy) |
| : CreateAndNavigateChildFrame(parent_driver, url, frame_info.policy, |
| frame_info.name); |
| if (!frame_info.name.empty()) { |
| CHECK(!base::Contains(drivers_, frame_info.name)); |
| drivers_.emplace(frame_info.name, driver); |
| } |
| |
| std::vector<FormData> forms; |
| for (const FormInfo& form_info : frame_info.forms) { |
| FormData data = form_info.form; |
| data.name = base::ASCIIToUTF16(form_info.name); |
| data.url = url; |
| for (FormFieldData& field : data.fields) |
| field.name = base::StrCat({data.name, u".", field.name}); |
| SetMetaData(data, driver->render_frame_host()); |
| |
| // Creates the frames and set their predecessor field according to |
| // FrameInfo::field_predecessor. By default, the frames come after all |
| // fields. |
| for (const FrameInfo& subframe_info : form_info.frames) { |
| MockContentAutofillDriver* child = |
| MockFormForest(subframe_info, driver, &data); |
| data.child_frames.emplace_back(); |
| data.child_frames.back().token = child->token(); |
| data.child_frames.back().predecessor = |
| std::min(static_cast<int>(data.fields.size()), |
| subframe_info.field_predecessor); |
| } |
| |
| if (!form_info.name.empty()) { |
| CHECK(!base::Contains(forms_, form_info.name)); |
| forms_.emplace(form_info.name, data.global_id()); |
| } |
| forms.push_back(data); |
| } |
| |
| auto frame_data = std::make_unique<FrameData>(driver->token()); |
| frame_data->child_forms = std::move(forms); |
| if (parent_form) |
| frame_data->parent_form = parent_form->global_id(); |
| frame_data->driver = driver; |
| auto p = frame_datas(mocked_forms_).insert(std::move(frame_data)); |
| CHECK(p.second); |
| return driver; |
| } |
| |
| using ForceReset = base::StrongAlias<struct ForceFlattenTag, bool>; |
| |
| // Mocks flattening of |form_fields| into their root form. |
| // |
| // Exactly one form mentioned in |form_fields| must be a root form in. The |
| // function flattens the fields specified by |form_fields| into this root, in |
| // the same order they appear in |form_fields|. |
| // |
| // MockFlattening() should be called only after MockFormForest(), as its first |
| // call copies the forms without their fields from |mocked_forms_| to |
| // |flattened_forms_|. |
| // |
| // To force such a copy in later calls (because the |flattened_forms_| may |
| // have changed in the meantime), set |force_flatten| to true. |
| void MockFlattening(const std::vector<FormSpan>& form_fields, |
| ForceReset force_flatten = ForceReset(false)) { |
| // Collect fields. |
| std::vector<FormFieldData> fields; |
| for (FormSpan f : form_fields) { |
| const FormData& source = GetMockedForm(f.form); |
| if (f.begin >= source.fields.size()) |
| continue; |
| if (f.begin + f.count > source.fields.size()) |
| f.count = base::dynamic_extent; |
| base::ranges::copy( |
| base::make_span(source.fields).subspan(f.begin, f.count), |
| std::back_inserter(fields)); |
| } |
| |
| // Copy |mocked_forms_| into |flattened_forms_|, without fields. |
| if (frame_datas(flattened_forms_).empty() || force_flatten) { |
| TestApi(flattened_forms_).Reset(); |
| std::vector<std::unique_ptr<FrameData>> copy; |
| for (const auto& frame : frame_datas(mocked_forms_)) { |
| copy.push_back(std::make_unique<FrameData>(frame->frame_token)); |
| copy.back()->parent_form = frame->parent_form; |
| copy.back()->child_forms = frame->child_forms; |
| for (FormData& child_form : copy.back()->child_forms) |
| child_form.fields.clear(); |
| copy.back()->driver = frame->driver; |
| } |
| frame_datas(flattened_forms_) = FrameDataSet(std::move(copy)); |
| } |
| |
| // Copy fields to the root. |
| auto IsRoot = [this](FormSpan fs) { |
| MockContentAutofillDriver* d = driver(fs.form); |
| return d->IsInAnyMainFrame() || d->is_sub_root(); |
| }; |
| auto it = base::ranges::find_if(form_fields, IsRoot); |
| CHECK(it != form_fields.end()); |
| CHECK(base::ranges::all_of(form_fields, [&](FormSpan fs) { |
| return !IsRoot(fs) || fs.form == it->form; |
| })); |
| GetFlattenedForm(it->form).fields = fields; |
| |
| // Validate flattening. |
| CHECK_EQ(frame_datas(flattened_forms_).size(), |
| frame_datas(mocked_forms_).size()); |
| auto IsRoorOrEmpty = [](const auto& frame) { |
| return !frame->parent_form || |
| base::ranges::all_of(frame->child_forms, |
| &std::vector<FormFieldData>::empty, |
| &FormData::fields); |
| }; |
| CHECK(base::ranges::all_of(frame_datas(flattened_forms_), IsRoorOrEmpty)); |
| } |
| |
| MockContentAutofillDriver* driver(base::StringPiece frame_or_form_name) { |
| auto it = drivers_.find(frame_or_form_name); |
| if (it != drivers_.end()) { |
| return it->second; |
| } else { |
| LocalFrameToken frame_token = |
| GetMockedForm(frame_or_form_name).host_frame; |
| const FrameData* frame_data = |
| TestApi(mocked_forms_).GetFrameData(frame_token); |
| return static_cast<MockContentAutofillDriver*>(frame_data->driver); |
| } |
| } |
| |
| FrameData& GetMockedFrame(base::StringPiece frame_or_form_name) { |
| MockContentAutofillDriver* d = driver(frame_or_form_name); |
| CHECK(d) << frame_or_form_name; |
| FrameData* frame = TestApi(mocked_forms_).GetFrameData(d->token()); |
| CHECK(frame); |
| return *frame; |
| } |
| |
| FormData& GetMockedForm(base::StringPiece form_name) { |
| auto it = forms_.find(form_name); |
| CHECK(it != forms_.end()) << form_name; |
| FormData* form = TestApi(mocked_forms_).GetFormData(it->second); |
| CHECK(form); |
| return *form; |
| } |
| |
| FormData& GetFlattenedForm(base::StringPiece form_name) { |
| CHECK(driver(form_name)->IsInAnyMainFrame() || |
| driver(form_name)->is_sub_root()); |
| auto it = forms_.find(form_name); |
| CHECK(it != forms_.end()) << form_name; |
| FormData* form = TestApi(flattened_forms_).GetFormData(it->second); |
| CHECK(form); |
| return *form; |
| } |
| |
| FormForest mocked_forms_; |
| FormForest flattened_forms_; |
| |
| private: |
| std::map<std::string, MockContentAutofillDriver*, std::less<>> drivers_; |
| std::map<std::string, FormGlobalId, std::less<>> forms_; |
| }; |
| |
| // Tests of FormForest::UpdateTreeOfRendererForm(). |
| |
| class FormForestTestUpdateTree : public FormForestTestWithMockedTree { |
| public: |
| // The subject of this test fixture. |
| void UpdateTreeOfRendererForm(FormForest& ff, base::StringPiece form_name) { |
| ff.UpdateTreeOfRendererForm(GetMockedForm(form_name), driver(form_name)); |
| } |
| }; |
| |
| // Tests that different root forms are not merged. |
| TEST_F(FormForestTestUpdateTree, MultipleRoots) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main1", .frames = {{.forms = {{.name = "child1"}}}}}, |
| {.name = "main2", .frames = {{.forms = {{.name = "child2"}}}}}}}); |
| MockFlattening({{"main1"}, {"child1"}}); |
| MockFlattening({{"main2"}, {"child2"}}); |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "main1"); |
| UpdateTreeOfRendererForm(ff, "main2"); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| // Tests that (only) for forms with unseen parent form TriggerFormExtraction is |
| // called on the parent frame. |
| TEST_F(FormForestTestUpdateTree, TriggerFormExtraction) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main1", .frames = {{.forms = {{.name = "child1"}}}}}, |
| {.name = "main2", .frames = {{.forms = {{.name = "child2"}}}}}}}); |
| MockFlattening({{"main1"}, {"child1"}}); |
| MockFlattening({{"main2"}, {"child2"}}); |
| FormForest ff; |
| EXPECT_CALL(*driver("main1"), TriggerFormExtraction).Times(1); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| EXPECT_CALL(*driver("main1"), TriggerFormExtraction).Times(0); |
| UpdateTreeOfRendererForm(ff, "main1"); |
| EXPECT_CALL(*driver("main1"), TriggerFormExtraction).Times(0); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| EXPECT_CALL(*driver("main2"), TriggerFormExtraction).Times(1); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| EXPECT_CALL(*driver("main2"), TriggerFormExtraction).Times(0); |
| UpdateTreeOfRendererForm(ff, "main2"); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| // Tests that at most 64 descendants are flattened into their root. |
| // |
| // The test creates a single root form (FormName(0)) with 30 child frames, each |
| // of which contains 3 forms, so there's a total of 90 forms. |
| // UpdateTreeOfRendererForm() flattens (only) the first 64 of these descendant |
| // forms. |
| TEST_F(FormForestTestUpdateTree, SizeLimit) { |
| auto FormName = [](size_t num) -> std::string { |
| return std::string("form") + base::NumberToString(num); |
| }; |
| // The number of maximum descendants (= node ranges) according to |
| // FormForest::UpdateTreeOfRendererForm()::kMaxVisits. |
| constexpr size_t kMaxFlattened = 64; |
| // The number of descendants that will actually get flattened. This may be |
| // less than kMaxFlattened because UpdateTreeOfRendererForm() either flattens |
| // all fields from a frame or none at all. |
| constexpr size_t kActualFlattened = kMaxFlattened / 3 * 3; |
| // The number of descendants we generate here, some of which will be flattened |
| // and some of which will not. |
| constexpr size_t kDescendants = 90; |
| static_assert(kActualFlattened < kMaxFlattened, ""); |
| static_assert(kDescendants % 3 == 0, ""); |
| |
| // Generate the tree with kDescendant child forms in groups of three per |
| // frame. Then detach the frames whose forms will not be flattened. |
| MockFormForest([&] { |
| FrameInfo root{.forms = {{.name = FormName(0)}}}; |
| for (size_t i = 0; i < kDescendants / 3; ++i) { |
| root.forms.front().frames.push_back( |
| {.forms = {{.name = FormName(3 * i + 1)}, |
| {.name = FormName(3 * i + 2)}, |
| {.name = FormName(3 * i + 3)}}}); |
| } |
| return root; |
| }()); |
| for (size_t i = kActualFlattened + 1; i <= kDescendants; ++i) { |
| driver(FormName(i))->set_sub_root(true); |
| GetMockedFrame(FormName(i)).parent_form = absl::nullopt; |
| } |
| |
| MockFlattening([&] { |
| std::vector<FormSpan> flattened_forms; |
| for (size_t i = 0; i <= kActualFlattened; ++i) |
| flattened_forms.push_back({FormName(i)}); |
| return flattened_forms; |
| }()); |
| for (size_t i = kActualFlattened + 1; i <= kDescendants; ++i) |
| MockFlattening({{FormName(i)}}); |
| |
| FormForest ff; |
| for (size_t i = 0; i <= kDescendants; ++i) |
| UpdateTreeOfRendererForm(ff, FormName(i)); |
| for (size_t i = kActualFlattened + 1; i <= kDescendants; i += 3) { |
| // At the time FormName(64) was seen, its frame contained only this one |
| // form, so the overall limit of kMaxDescendants was satisfied. Therefore, |
| // its fields were moved to the root and then deleted from the root once |
| // another form was seen and the limit was exceeded. We need to see the form |
| // again to reinstate its fields. |
| // |
| // The same holds for field 67 in the next frame, and so on. |
| UpdateTreeOfRendererForm(ff, FormName(i + 0)); |
| // At the time FormName(65) was seen, its frame contained contained two |
| // forms, so the overall limit of kMaxDescendants wasn't satisfied anymore. |
| // Therefore, its fields weren't moved to the root. However, its fields had |
| // been moved to a temporary variable and then lost. We need to see the form |
| // again to reinstate its fields. |
| // |
| // The same holds for field 68 in the next frame, and so on. |
| UpdateTreeOfRendererForm(ff, FormName(i + 1)); |
| // We don't need to see FormName(66) again because already when FormName(65) |
| // was seen, the frame's FrameData::parent_form was unset, so FormName(66) |
| // was handled as any ordinary (root) form of a (sub)tree. |
| // |
| // The same holds for field 69 in the next frame, and so on. |
| } |
| |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| using FormNameVector = std::vector<std::string>; |
| |
| // Parameterized by a list of forms, which in this order are added to the |
| // FormForest. |
| // Note that among the forms from the same form, the order of calling |
| // UpdateTreeOfRendererForm() matters. |
| // Hence, when generating permutations, use FlattenedPermutations() to keep the |
| // forms from the same frame in stable order. |
| class FormForestTestUpdateOrder |
| : public FormForestTestUpdateTree, |
| public ::testing::WithParamInterface<FormNameVector> { |
| protected: |
| void TearDown() override { |
| TestApi(ff_).Reset(); |
| FormForestTestUpdateTree::TearDown(); |
| } |
| |
| void UpdateFormForestAccordingToParamOrder() { |
| for (const std::string& form_name : GetParam()) |
| UpdateTreeOfRendererForm(ff_, form_name); |
| } |
| |
| FormForest ff_; |
| }; |
| |
| class FormForestTestUpdateVerticalOrder : public FormForestTestUpdateOrder {}; |
| |
| // Tests that children and grandchildren are merged into their root form. |
| TEST_P(FormForestTestUpdateVerticalOrder, Test) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main", |
| .frames = { |
| {.url = kIframeUrl, |
| .forms = {{.name = "inner", |
| .frames = {{.forms = {{.name = "leaf"}}}}}}}}}}}); |
| MockFlattening({{"main"}, {"inner"}, {"leaf"}}); |
| UpdateFormForestAccordingToParamOrder(); |
| EXPECT_THAT(ff_, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateVerticalOrder, |
| testing::ValuesIn(Permutations(FormNameVector{ |
| "main", "inner", "leaf"}))); |
| |
| class FormForestTestUpdateHorizontalMultiFormSingleFrameOrder |
| : public FormForestTestUpdateOrder {}; |
| |
| // Tests that siblings from the same frames are merged into their root form. |
| TEST_P(FormForestTestUpdateHorizontalMultiFormSingleFrameOrder, Test) { |
| MockFormForest({.forms = {{.name = "main", |
| .frames = {{.forms = {{.name = "child1"}, |
| {.name = "child2"}}}}}}}); |
| MockFlattening({{"main"}, {"child1"}, {"child2"}}); |
| UpdateFormForestAccordingToParamOrder(); |
| EXPECT_THAT(ff_, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FormForestTest, |
| FormForestTestUpdateHorizontalMultiFormSingleFrameOrder, |
| testing::ValuesIn(FlattenedPermutations( |
| std::vector<std::vector<std::string>>{{"main"}, |
| {"child1", "child2"}}))); |
| |
| class FormForestTestUpdateHorizontalMultiFrameSingleFormOrder |
| : public FormForestTestUpdateOrder {}; |
| |
| // Tests that siblings from different frames are merged into their root form. |
| TEST_P(FormForestTestUpdateHorizontalMultiFrameSingleFormOrder, Test) { |
| MockFormForest({.forms = {{.name = "main", |
| .frames = {{.forms = {{.name = "child1"}}}, |
| {.forms = {{.name = "child2"}}}}}}}); |
| MockFlattening({{"main"}, {"child1"}, {"child2"}}); |
| UpdateFormForestAccordingToParamOrder(); |
| EXPECT_THAT(ff_, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FormForestTest, |
| FormForestTestUpdateHorizontalMultiFrameSingleFormOrder, |
| testing::ValuesIn(Permutations(FormNameVector{"main", "child1", |
| "child2"}))); |
| |
| class FormForestTestUpdateHorizontalMultiFormMultiFrameOrder |
| : public FormForestTestUpdateOrder {}; |
| |
| // Tests that siblings from multiple and the same frame are merged into their |
| // root form. |
| TEST_P(FormForestTestUpdateHorizontalMultiFormMultiFrameOrder, Test) { |
| auto url = [](base::StringPiece path) { // Needed due to crbug/1217402. |
| return base::StrCat({kMainUrl, path}); |
| }; |
| MockFormForest( |
| {.url = url("main"), |
| .forms = {{.name = "main", |
| .frames = {{.url = url("child1+2"), |
| .forms = {{.name = "child1"}, {.name = "child2"}}, |
| .field_predecessor = -1}, |
| {.url = url("child3+4"), |
| .forms = {{.name = "child3"}, {.name = "child4"}}, |
| .field_predecessor = 5}}}}}); |
| MockFlattening({{"child1"}, {"child2"}, {"main"}, {"child3"}, {"child4"}}); |
| UpdateFormForestAccordingToParamOrder(); |
| EXPECT_THAT(ff_, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateHorizontalMultiFormMultiFrameOrder, |
| testing::ValuesIn(FlattenedPermutations( |
| std::vector<std::vector<std::string>>{ |
| {"main"}, |
| {"child1", "child2"}, |
| {"child3", "child4"}}))); |
| |
| using ChildFramePredecessors = std::tuple<int, int, int>; |
| |
| // Parameterized by the indices of the fields that precede child frames. |
| class FormForestTestUpdateSplitForm |
| : public FormForestTestUpdateTree, |
| public ::testing::WithParamInterface<ChildFramePredecessors> {}; |
| |
| // Tests that fields of subforms are inserted into the parent form at the |
| // index as specified by FormData::child_frame_predecessors. |
| TEST_P(FormForestTestUpdateSplitForm, Test) { |
| int field0 = std::get<0>(GetParam()); |
| int field1 = std::get<1>(GetParam()); |
| int field2 = std::get<2>(GetParam()); |
| ASSERT_LE(-1, field0); |
| ASSERT_LE(field0, field1); |
| ASSERT_LE(field1, field2); |
| MockFormForest( |
| {.forms = {{.name = "main", |
| .frames = {{.forms = {{.name = "child1"}, {.name = "child2"}}, |
| .field_predecessor = field0}, |
| {.forms = {{.name = "child3"}, {.name = "child4"}}, |
| .field_predecessor = field1}, |
| {.forms = {{.name = "child5"}, {.name = "child6"}}, |
| .field_predecessor = field2}}}}}); |
| MockFlattening({{.form = "main", .count = base::as_unsigned(field0 + 1)}, |
| {"child1"}, |
| {"child2"}, |
| {.form = "main", |
| .begin = base::as_unsigned(field0 + 1), |
| .count = base::as_unsigned(field1 - field0)}, |
| {"child3"}, |
| {"child4"}, |
| {.form = "main", |
| .begin = base::as_unsigned(field1 + 1), |
| .count = base::as_unsigned(field2 - field1)}, |
| {"child5"}, |
| {"child6"}, |
| {.form = "main", .begin = base::as_unsigned(field2 + 1)}}); |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "child3"); |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "child4"); |
| UpdateTreeOfRendererForm(ff, "child5"); |
| UpdateTreeOfRendererForm(ff, "child6"); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateSplitForm, |
| testing::Values(ChildFramePredecessors{-1, -1, -1}, |
| ChildFramePredecessors{-1, 2, 5}, |
| ChildFramePredecessors{1, 1, 1}, |
| ChildFramePredecessors{3, 3, 3}, |
| ChildFramePredecessors{5, 5, 5})); |
| |
| class FormForestTestUpdateComplexOrder : public FormForestTestUpdateOrder {}; |
| |
| // Tests for a complex tree that all descendants are merged into their root. |
| TEST_P(FormForestTestUpdateComplexOrder, Test) { |
| auto url = [](base::StringPiece path) { // Needed due to crbug/1217402. |
| return base::StrCat({kMainUrl, path}); |
| }; |
| MockFormForest( |
| {.url = url("main"), |
| .forms = { |
| {.name = "main", |
| .frames = { |
| {.url = url("children"), |
| .forms = {{.name = "child1", |
| .frames = {{.url = url("grandchild1+2"), |
| .forms = {{.name = "grandchild1"}, |
| {.name = "grandchild2"}}, |
| .field_predecessor = -1}, |
| {.url = url("grandchild3+4"), |
| .forms = {{.name = "grandchild3"}, |
| {.name = "grandchild4"}}, |
| .field_predecessor = 5}}}, |
| {.name = "child2"}}, |
| .field_predecessor = 2}}}}}); |
| MockFlattening({{.form = "main", .count = 3}, |
| {"grandchild1"}, |
| {"grandchild2"}, |
| {"child1"}, |
| {"grandchild3"}, |
| {"grandchild4"}, |
| {"child2"}, |
| {.form = "main", .begin = 3}}); |
| UpdateFormForestAccordingToParamOrder(); |
| EXPECT_THAT(ff_, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateComplexOrder, |
| testing::ValuesIn(FlattenedPermutations( |
| std::vector<std::vector<std::string>>{ |
| {"main"}, |
| {"child1", "child2"}, |
| {"grandchild1", "grandchild2"}, |
| {"grandchild3", "grandchild4"}}))); |
| |
| // Tests that erasing a form removes the form and its fields. |
| TEST_F(FormForestTestUpdateTree, EraseForm_FieldRemoval) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main", |
| .frames = { |
| {.url = kIframeUrl, |
| .forms = {{.name = "inner", |
| .frames = {{.forms = {{.name = "leaf"}}}}}}}}}}}); |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "inner"); |
| UpdateTreeOfRendererForm(ff, "leaf"); |
| FormGlobalId removed_form = GetMockedForm("leaf").global_id(); |
| EXPECT_THAT(ff.EraseForms(std::array{removed_form}), |
| ElementsAre(GetMockedForm("main").global_id())); |
| base::EraseIf( |
| (*frame_datas(mocked_forms_).find(removed_form.frame_token))->child_forms, |
| [&](const FormData& form) { return form.global_id() == removed_form; }); |
| MockFlattening({{"main"}, {"inner"}}); |
| ASSERT_EQ(GetFlattenedForm("main").fields.size(), 12u); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| // Tests that erasing a frame unsets the children's FrameData::parent_form |
| // pointer. |
| TEST_F(FormForestTestUpdateTree, EraseForm_ParentReset) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main", |
| .frames = { |
| {.url = kIframeUrl, |
| .forms = {{.name = "inner", |
| .frames = {{.forms = {{.name = "leaf"}}}}}}}}}}}); |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "inner"); |
| UpdateTreeOfRendererForm(ff, "leaf"); |
| FormGlobalId removed_form = GetMockedForm("inner").global_id(); |
| EXPECT_THAT(ff.EraseForms(std::array{removed_form}), |
| ElementsAre(GetMockedForm("main").global_id())); |
| base::EraseIf( |
| (*frame_datas(mocked_forms_).find(removed_form.frame_token))->child_forms, |
| [&](const FormData& form) { return form.global_id() == removed_form; }); |
| driver("leaf")->set_sub_root(true); |
| GetMockedFrame("leaf").parent_form = absl::nullopt; |
| MockFlattening({{"main"}}); |
| MockFlattening({{"leaf"}}); |
| base::ranges::copy(GetFlattenedForm("leaf").fields, |
| std::back_inserter(GetFlattenedForm("main").fields)); |
| GetFlattenedForm("leaf").fields.clear(); |
| ASSERT_EQ(GetFlattenedForm("main").fields.size(), 12u); |
| ASSERT_EQ(GetFlattenedForm("leaf").fields.size(), 0u); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| class FormForestTestUpdateEraseFrame |
| : public FormForestTestUpdateTree, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| bool keep_frame() const { return GetParam(); } |
| }; |
| |
| // Tests that erasing a frame removes its form and fields. |
| TEST_P(FormForestTestUpdateEraseFrame, EraseFrame_FieldRemoval) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main", |
| .frames = { |
| {.url = kIframeUrl, |
| .forms = {{.name = "inner", |
| .frames = {{.forms = {{.name = "leaf"}}}}}}}}}}}); |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "inner"); |
| UpdateTreeOfRendererForm(ff, "leaf"); |
| ff.EraseFormsOfFrame(GetMockedForm("leaf").host_frame, |
| /*keep_frame=*/keep_frame()); |
| if (!keep_frame()) { |
| frame_datas(mocked_forms_).erase(GetMockedForm("leaf").host_frame); |
| } else { |
| (*frame_datas(mocked_forms_).find(GetMockedForm("leaf").host_frame)) |
| ->child_forms.clear(); |
| } |
| MockFlattening({{"main"}, {"inner"}}); |
| ASSERT_EQ(GetFlattenedForm("main").fields.size(), 12u); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| // Tests that erasing a frame unsets the children's FrameData::parent_form |
| // pointer. |
| TEST_P(FormForestTestUpdateEraseFrame, EraseFrame_ParentReset) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main", |
| .frames = { |
| {.url = kIframeUrl, |
| .forms = {{.name = "inner", |
| .frames = {{.forms = {{.name = "leaf"}}}}}}}}}}}); |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "inner"); |
| UpdateTreeOfRendererForm(ff, "leaf"); |
| ff.EraseFormsOfFrame(GetMockedForm("inner").host_frame, |
| /*keep_frame=*/keep_frame()); |
| if (!keep_frame()) { |
| frame_datas(mocked_forms_).erase(GetMockedForm("inner").host_frame); |
| } else { |
| (*frame_datas(mocked_forms_).find(GetMockedForm("inner").host_frame)) |
| ->child_forms.clear(); |
| } |
| driver("leaf")->set_sub_root(true); |
| GetMockedFrame("leaf").parent_form = absl::nullopt; |
| MockFlattening({{"main"}}); |
| MockFlattening({{"leaf"}}); |
| base::ranges::copy(GetFlattenedForm("leaf").fields, |
| std::back_inserter(GetFlattenedForm("main").fields)); |
| GetFlattenedForm("leaf").fields.clear(); |
| ASSERT_EQ(GetFlattenedForm("main").fields.size(), 12u); |
| ASSERT_EQ(GetFlattenedForm("leaf").fields.size(), 0u); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateEraseFrame, |
| testing::Bool()); |
| |
| // Parameterized with a source and an optional target form and field index. |
| class FormForestTestUpdateFieldChange : public FormForestTestUpdateTree { |
| protected: |
| void MockFormForest() { |
| auto url = [](base::StringPiece path) { // Needed due to crbug/1217402. |
| return base::StrCat({kMainUrl, path}); |
| }; |
| FormForestTestWithMockedTree::MockFormForest( |
| {.url = url("main"), |
| .forms = { |
| {.name = "main", |
| .frames = {{.url = url("child1+2"), |
| .forms = {{.name = "child1"}, {.name = "child2"}}, |
| .field_predecessor = -1}, |
| {.url = url("child3+4"), |
| .forms = {{.name = "child3"}, {.name = "child4"}}, |
| .field_predecessor = 2}, |
| {.url = url("child5+6"), |
| .forms = {{.name = "child5"}, {.name = "child6"}}, |
| .field_predecessor = 5}}}}}); |
| } |
| |
| void MockFlattening() { |
| FormForestTestWithMockedTree::MockFlattening( |
| {{"child1"}, |
| {"child2"}, |
| {.form = "main", .count = 3}, |
| {"child3"}, |
| {"child4"}, |
| {.form = "main", .begin = 3, .count = 3}, |
| {"child5"}, |
| {"child6"}, |
| {.form = "main", .begin = 6}}); |
| } |
| |
| void UpdateTreeOfAllForms(FormForest& ff) { |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "child3"); |
| UpdateTreeOfRendererForm(ff, "child4"); |
| UpdateTreeOfRendererForm(ff, "child5"); |
| UpdateTreeOfRendererForm(ff, "child6"); |
| } |
| }; |
| |
| struct FieldSpec { |
| std::string form_name; |
| size_t field_index; |
| }; |
| |
| // Removes a field according to the parameter. |
| class FormForestTestUpdateFieldRemove |
| : public FormForestTestUpdateFieldChange, |
| public ::testing::WithParamInterface<FieldSpec> { |
| protected: |
| void DoRemove() { |
| FormData& source_form = GetMockedForm(GetParam().form_name); |
| size_t source_index = GetParam().field_index; |
| source_form.fields.erase(source_form.fields.begin() + source_index); |
| } |
| }; |
| |
| // Tests that removing fields from a form is reflected in the form tree. |
| TEST_P(FormForestTestUpdateFieldRemove, Test) { |
| MockFormForest(); |
| MockFlattening(); |
| FormForest ff; |
| UpdateTreeOfAllForms(ff); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| DoRemove(); |
| MockFlattening(); |
| UpdateTreeOfRendererForm(ff, GetParam().form_name); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateFieldRemove, |
| testing::Values(FieldSpec{"main", 2}, |
| FieldSpec{"main", 3}, |
| FieldSpec{"child1", 0}, |
| FieldSpec{"child2", 1}, |
| FieldSpec{"child3", 2}, |
| FieldSpec{"child4", 3}, |
| FieldSpec{"child5", 4}, |
| FieldSpec{"child6", 5})); |
| |
| // Adds a new field according to the parameter. |
| class FormForestTestUpdateFieldAdd |
| : public FormForestTestUpdateFieldChange, |
| public ::testing::WithParamInterface<FieldSpec> { |
| protected: |
| void DoAdd() { |
| FormData& target_form = GetMockedForm(GetParam().form_name); |
| size_t target_index = GetParam().field_index; |
| FormFieldData field = target_form.fields.front(); |
| field.name = base::StrCat({field.name, u"_copy"}); |
| field.unique_renderer_id = test::MakeFieldRendererId(); |
| target_form.fields.insert(target_form.fields.begin() + target_index, field); |
| } |
| }; |
| |
| // Tests that adding a field to forms is reflected in the form tree. |
| TEST_P(FormForestTestUpdateFieldAdd, Test) { |
| MockFormForest(); |
| MockFlattening(); |
| FormForest ff; |
| UpdateTreeOfAllForms(ff); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| DoAdd(); |
| MockFlattening(); |
| UpdateTreeOfRendererForm(ff, GetParam().form_name); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestUpdateFieldAdd, |
| testing::Values(FieldSpec{"main", 2}, |
| FieldSpec{"main", 3}, |
| FieldSpec{"child1", 0}, |
| FieldSpec{"child2", 1}, |
| FieldSpec{"child3", 2}, |
| FieldSpec{"child4", 3}, |
| FieldSpec{"child5", 4}, |
| FieldSpec{"child6", 5})); |
| |
| struct FieldMoveSpec { |
| FieldSpec source; |
| FieldSpec target; |
| }; |
| |
| // Moves a field from one form to another according to the parameter. |
| class FormForestTestUpdateFieldMove |
| : public FormForestTestUpdateFieldChange, |
| public ::testing::WithParamInterface<FieldMoveSpec> { |
| protected: |
| void DoMove() { |
| const FieldMoveSpec& p = GetParam(); |
| FormData& source_form = GetMockedForm(p.source.form_name); |
| size_t source_index = p.source.field_index; |
| FormData& target_form = GetMockedForm(p.target.form_name); |
| size_t target_index = p.target.field_index; |
| |
| FormFieldData field = source_form.fields[source_index]; |
| field.host_form_id = target_form.unique_renderer_id; |
| |
| if (source_index > target_index) { |
| source_form.fields.erase(source_form.fields.begin() + source_index); |
| target_form.fields.insert(target_form.fields.begin() + target_index, |
| field); |
| } else { |
| target_form.fields.insert(target_form.fields.begin() + target_index, |
| field); |
| source_form.fields.erase(source_form.fields.begin() + source_index); |
| } |
| } |
| }; |
| |
| // Tests that moving fields between forms (of the same frame) is reflected in |
| // the form tree. |
| TEST_P(FormForestTestUpdateFieldMove, Test) { |
| MockFormForest(); |
| MockFlattening(); |
| FormForest ff; |
| UpdateTreeOfAllForms(ff); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| DoMove(); |
| MockFlattening(); |
| UpdateTreeOfRendererForm(ff, GetParam().source.form_name); |
| if (GetParam().source.form_name != GetParam().target.form_name) |
| UpdateTreeOfRendererForm(ff, GetParam().target.form_name); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FormForestTest, |
| FormForestTestUpdateFieldMove, |
| testing::Values(FieldMoveSpec{{"main", 0}, {"main", 5}}, |
| FieldMoveSpec{{"main", 5}, {"main", 0}}, |
| FieldMoveSpec{{"child1", 0}, {"child1", 5}}, |
| FieldMoveSpec{{"child1", 5}, {"child1", 0}}, |
| FieldMoveSpec{{"child1", 1}, {"child1", 4}}, |
| FieldMoveSpec{{"child1", 3}, {"child2", 3}}, |
| FieldMoveSpec{{"child3", 5}, {"child4", 0}}, |
| FieldMoveSpec{{"child6", 5}, {"child5", 0}})); |
| |
| // Tests that UpdateTreeOfRendererForm() converges, that is, multiple calls are |
| // no-ops. |
| TEST_F(FormForestTestUpdateTree, Converge) { |
| auto url = [](base::StringPiece path) { // Needed due to crbug/1217402. |
| return base::StrCat({kMainUrl, path}); |
| }; |
| MockFormForest( |
| {.url = url("main"), |
| .forms = { |
| {.name = "main", |
| .frames = { |
| {.url = url("children"), |
| .forms = {{.name = "child1", |
| .frames = {{.url = url("grandchild1"), |
| .forms = {{.name = "grandchild1"}}}, |
| {.url = url("grandchild2"), |
| .forms = {{.name = "grandchild2"}}}}}, |
| {.name = "child2"}}}}}}}); |
| MockFlattening( |
| {{"main"}, {"child1"}, {"grandchild1"}, {"grandchild2"}, {"child2"}}); |
| |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "grandchild1"); |
| UpdateTreeOfRendererForm(ff, "grandchild2"); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "grandchild1"); |
| UpdateTreeOfRendererForm(ff, "grandchild2"); |
| UpdateTreeOfRendererForm(ff, "grandchild2"); |
| UpdateTreeOfRendererForm(ff, "grandchild1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "main"); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| // Tests that removing a frame from FormData::child_frames removes the fields |
| // (but not the FrameData; this is taken care of by EraseFormsOfFrame()). |
| TEST_F(FormForestTestUpdateTree, RemoveFrame) { |
| auto url = [](base::StringPiece path) { // Needed due to crbug/1217402. |
| return base::StrCat({kMainUrl, path}); |
| }; |
| // |child1| is a separate variable for better code formatting. |
| FormInfo child1 = { |
| .name = "child1", |
| .frames = { |
| {.url = url("grandchild1"), .forms = {{.name = "grandchild1"}}}, |
| {.url = url("grandchild2"), |
| .forms = {{.name = "grandchild2", |
| .frames = {{.url = url("greatgrandchild"), |
| .forms = {{.name = "greatgrandchild"}}}}}}}}}; |
| MockFormForest( |
| {.url = url("main"), |
| .forms = {{.name = "main", |
| .frames = {{.url = url("children"), |
| .forms = {child1, {.name = "child2"}}}}}}}); |
| MockFlattening({{"main"}, |
| {"child1"}, |
| {"grandchild1"}, |
| {"grandchild2"}, |
| {"greatgrandchild"}, |
| {"child2"}}); |
| |
| FormForest ff; |
| UpdateTreeOfRendererForm(ff, "main"); |
| UpdateTreeOfRendererForm(ff, "child1"); |
| UpdateTreeOfRendererForm(ff, "child2"); |
| UpdateTreeOfRendererForm(ff, "grandchild1"); |
| UpdateTreeOfRendererForm(ff, "grandchild2"); |
| UpdateTreeOfRendererForm(ff, "greatgrandchild"); |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| ASSERT_EQ(GetFlattenedForm("main").fields.size(), 6u * 6u); |
| |
| // Remove the last frame of "child1", which contains "grandchild2" and |
| // indirectly "greatgrandchild". |
| driver("grandchild2")->set_sub_root(true); |
| GetMockedForm("child1").child_frames.pop_back(); |
| GetMockedFrame("grandchild2").parent_form = absl::nullopt; |
| GetMockedForm("grandchild2").fields.clear(); |
| GetMockedForm("greatgrandchild").fields.clear(); |
| MockFlattening({{"main"}, {"child1"}, {"grandchild1"}, {"child2"}}, |
| ForceReset(true)); |
| MockFlattening({{"grandchild2"}, {"greatgrandchild"}}); |
| ASSERT_EQ(GetFlattenedForm("main").fields.size(), 4u * 6u); |
| ASSERT_EQ(GetFlattenedForm("grandchild2").fields.size(), 0u); |
| |
| UpdateTreeOfRendererForm(ff, "child1"); |
| |
| EXPECT_THAT(ff, Equals(flattened_forms_)); |
| } |
| |
| // Tests of FormForest::GetBrowserForm(). |
| |
| class FormForestTestFlatten : public FormForestTestWithMockedTree { |
| protected: |
| // The subject of this test fixture. |
| FormData GetBrowserForm(base::StringPiece form_name) { |
| return flattened_forms_.GetBrowserForm( |
| GetMockedForm(form_name).global_id()); |
| } |
| }; |
| |
| // Tests that flattening a single frame is the identity. |
| TEST_F(FormForestTestFlatten, SingleFrame) { |
| MockFormForest({.url = kMainUrl, .forms = {{.name = "main"}}}); |
| MockFlattening({{"main"}}); |
| EXPECT_THAT(GetBrowserForm("main"), Equals(GetFlattenedForm("main"))); |
| } |
| |
| class FormForestTestFlattenHierarchy |
| : public FormForestTestFlatten, |
| public ::testing::WithParamInterface<std::string> {}; |
| |
| // Tests that a non-trivial tree is flattened into the root. |
| TEST_P(FormForestTestFlattenHierarchy, TwoFrames) { |
| MockFormForest( |
| {.forms = { |
| {.name = "main", |
| .frames = {{.forms = {{.name = "child1"}, {.name = "child2"}}}}}, |
| {.name = "main2", |
| .frames = {{.forms = {{.name = "child3"}, {.name = "child4"}}}}}}}); |
| MockFlattening({{"main"}, {"child1"}, {"child2"}}); |
| MockFlattening({{"main2"}, {"child3"}, {"child4"}}); |
| EXPECT_THAT(GetBrowserForm(GetParam()), Equals(GetFlattenedForm("main"))); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FormForestTest, |
| FormForestTestFlattenHierarchy, |
| testing::Values("main", "child1", "child2")); |
| |
| // Tests of FormForest::GetRendererFormsOfBrowserForm(). |
| |
| class FormForestTestUnflatten : public FormForestTestWithMockedTree { |
| protected: |
| // The subject of this test fixture. |
| std::vector<FormData> GetRendererFormsOfBrowserForm( |
| base::StringPiece form_name, |
| const url::Origin& triggered_origin, |
| const base::flat_map<FieldGlobalId, ServerFieldType>& field_type_map) { |
| return flattened_forms_ |
| .GetRendererFormsOfBrowserForm(WithValues(GetFlattenedForm(form_name)), |
| triggered_origin, field_type_map) |
| .renderer_forms; |
| } |
| |
| auto FieldTypeMap(base::StringPiece form_name) { |
| return CreateFieldTypeMap(WithValues(GetFlattenedForm(form_name))); |
| } |
| }; |
| |
| // Test that solitaire main frame forms are filled as usual. |
| TEST_F(FormForestTestUnflatten, MainFrame) { |
| MockFormForest({.url = kMainUrl, |
| .forms = {{.name = "main", .frames = {}}, |
| {.name = "main2", .frames = {}}}}); |
| MockFlattening({{"main"}}); |
| MockFlattening({{"main2"}}); |
| std::vector<FormData> expectation = {WithValues(GetMockedForm("main"))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kMainUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Test that child frame forms are filled as usual. |
| TEST_F(FormForestTestUnflatten, ChildFrame) { |
| MockFormForest({.url = kMainUrl, |
| .forms = {{.name = "main", |
| .frames = {{.url = kIframeUrl, |
| .forms = {{.name = "child"}}}}}}}); |
| MockFlattening({{"main"}, {"child"}}); |
| std::vector<FormData> expectation = { |
| GetMockedForm("main"), WithValues(GetMockedForm("child"), Profile(1))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kIframeUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Test that a tree of forms is filled (assuming same origins), but other |
| // neighboring trees are not. |
| TEST_F(FormForestTestUnflatten, LargeTree) { |
| auto url = [](base::StringPiece path) { // Needed due to crbug/1217402. |
| return base::StrCat({kMainUrl, path}); |
| }; |
| MockFormForest( |
| {.url = url("main"), |
| .forms = |
| {{.name = "main", |
| .frames = {{.url = url("children"), |
| .forms = |
| {{.name = "child1", |
| .frames = {{.url = url("grandchild1+2"), |
| .forms = {{.name = "grandchild1"}, |
| {.name = "grandchild2"}}}, |
| {.url = url("grandchild3+4"), |
| .forms = {{.name = "grandchild3"}, |
| {.name = |
| "grandchild4"}}}}}, |
| {.name = "child2"}}}}}, |
| {.name = "main2", |
| .frames = { |
| {.url = url("nieces"), |
| .forms = {{.name = "niece1"}, {.name = "niece2"}}}}}}}); |
| MockFlattening({{"main"}, |
| {"grandchild1"}, |
| {"grandchild2"}, |
| {"child1"}, |
| {"grandchild3"}, |
| {"grandchild4"}, |
| {"child2"}}); |
| MockFlattening({{"main2"}, {"niece1"}, {"niece2"}}); |
| std::vector<FormData> expectation = { |
| WithValues(GetMockedForm("main"), Profile(0)), |
| WithValues(GetMockedForm("grandchild1"), Profile(1)), |
| WithValues(GetMockedForm("grandchild2"), Profile(2)), |
| WithValues(GetMockedForm("child1"), Profile(3)), |
| WithValues(GetMockedForm("grandchild3"), Profile(4)), |
| WithValues(GetMockedForm("grandchild4"), Profile(5)), |
| WithValues(GetMockedForm("child2"), Profile(6))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kMainUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Tests that (only) frames from the same origin are filled. |
| TEST_F(FormForestTestUnflatten, SameOriginPolicy) { |
| MockFormForest( |
| {.url = kMainUrl, |
| .forms = { |
| {.name = "main", |
| .frames = {{.url = kOtherUrl, .forms = {{.name = "child1"}}}, |
| {.url = kIframeUrl, .forms = {{.name = "child2"}}}}}}}); |
| MockFlattening({{"main"}, {"child1"}, {"child2"}}); |
| std::vector<FormData> expectation = { |
| WithoutValues(GetMockedForm("main")), |
| WithoutValues(GetMockedForm("child1")), |
| WithValues(GetMockedForm("child2"), Profile(2))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kIframeUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Tests that even if a different-origin frame interrupts two same-origin |
| // frames, they are filled together. |
| TEST_F(FormForestTestUnflatten, InterruptedSameOriginPolicy) { |
| MockFormForest( |
| {.url = kMainUrl, |
| .forms = { |
| {.name = "main", |
| .frames = { |
| {.url = kIframeUrl, |
| .forms = {{.name = "inner", |
| .frames = {{.url = kMainUrl, |
| .forms = {{.name = "leaf"}}}}}}}}}}}); |
| MockFlattening({{"main"}, {"inner"}, {"leaf"}}); |
| std::vector<FormData> expectation = { |
| WithValues(GetMockedForm("main"), Profile(0)), |
| WithoutValues(GetMockedForm("inner")), |
| WithValues(GetMockedForm("leaf"), Profile(2))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kMainUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Tests that (only) non-sensitive fields are filled across origin into the main |
| // frame's origin (since the main frame has the shared-autofill policy by |
| // default). |
| TEST_F(FormForestTestUnflatten, MainOriginPolicy) { |
| MockFormForest( |
| {.url = kMainUrl, |
| .forms = { |
| {.name = "main", |
| .frames = {{.url = kMainUrl, .forms = {{.name = "child1"}}}, |
| {.url = kIframeUrl, .forms = {{.name = "child2"}}}}}}}); |
| MockFlattening({{"main"}, {"child1"}, {"child2"}}); |
| std::vector<FormData> expectation = { |
| WithValues(GetMockedForm("main"), Profile(0)), |
| WithValues(GetMockedForm("child1"), Profile(1)), |
| WithValues(GetMockedForm("child2"), Profile(2))}; |
| // Clear sensitive fields: the credit card number (field index 2) and CVC |
| // (field index 5) in the two main-origin forms. |
| expectation[0].fields[2].value.clear(); |
| expectation[0].fields[5].value.clear(); |
| expectation[1].fields[2].value.clear(); |
| expectation[1].fields[5].value.clear(); |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kIframeUrl), |
| FieldTypeMap("main")), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Tests that no fields are filled across origin into frames where |
| // shared-autofill is disabled (not even into non-sensitive fields). |
| TEST_F(FormForestTestUnflatten, MainOriginPolicyWithoutSharedAutofill) { |
| MockFormForest( |
| {.url = kMainUrl, |
| .forms = {{.name = "main", |
| .frames = {{.url = kMainUrl, .forms = {{.name = "child1"}}}, |
| {.url = kIframeUrl, |
| .forms = {{.name = "child2"}}}}}}, |
| .policy = Policy::kNoSharedAutofill}); |
| MockFlattening({{"main"}, {"child1"}, {"child2"}}); |
| std::vector<FormData> expectation = { |
| WithoutValues(GetMockedForm("main")), |
| WithoutValues(GetMockedForm("child1")), |
| WithValues(GetMockedForm("child2"), Profile(2))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kIframeUrl), |
| FieldTypeMap("main")), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Fixture for the shared-autofill policy tests. |
| class FormForestTestUnflattenSharedAutofillPolicy |
| : public FormForestTestUnflatten { |
| public: |
| void SetUp() override { |
| FormForestTestUnflatten::SetUp(); |
| MockFormForest( |
| {.url = kMainUrl, |
| .forms = { |
| {.name = "main", |
| .frames = {{.url = kOtherUrl, .forms = {{.name = "disallowed"}}}, |
| {.url = kIframeUrl, |
| .forms = {{.name = "allowed"}}, |
| .policy = Policy::kSharedAutofill}}}}}); |
| ASSERT_NE(Origin("main"), Origin("allowed")); |
| ASSERT_NE(Origin("disallowed"), Origin("allowed")); |
| } |
| }; |
| |
| // Tests filling into frames with shared-autofill policy from the main origin. |
| TEST_F(FormForestTestUnflattenSharedAutofillPolicy, FromMainOrigin) { |
| MockFlattening({{"main"}, {"disallowed"}, {"allowed"}}); |
| std::vector<FormData> expectation = { |
| WithValues(GetMockedForm("main"), Profile(0)), |
| WithoutValues(GetMockedForm("disallowed")), |
| WithValues(GetMockedForm("allowed"), Profile(2))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kMainUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Tests filling into frames with shared-autofill policy from the main origin. |
| TEST_F(FormForestTestUnflattenSharedAutofillPolicy, FromOtherOrigin) { |
| MockFlattening({{"main"}, {"disallowed"}, {"allowed"}}); |
| std::vector<FormData> expectation = { |
| WithoutValues(GetMockedForm("main")), |
| WithValues(GetMockedForm("disallowed"), Profile(1)), |
| WithoutValues(GetMockedForm("allowed"))}; |
| EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kOtherUrl), {}), |
| UnorderedArrayEquals(expectation)); |
| } |
| |
| // Tests irreflexivity, asymmetry, transitivity of FrameData less-than relation. |
| TEST_F(FormForestTest, FrameDataComparator) { |
| FrameData::CompareByFrameToken less; |
| std::unique_ptr<FrameData> null; |
| auto x = std::make_unique<FrameData>(test::MakeLocalFrameToken()); |
| auto xx = std::make_unique<FrameData>(x->frame_token); |
| auto y = std::make_unique<FrameData>(test::MakeLocalFrameToken()); |
| ASSERT_TRUE(x->frame_token < y->frame_token); |
| EXPECT_FALSE(less(null, null)); |
| EXPECT_TRUE(less(null, x)); |
| EXPECT_FALSE(less(x, null)); |
| EXPECT_FALSE(less(x, x)); |
| EXPECT_FALSE(less(xx, xx)); |
| EXPECT_FALSE(less(x, xx)); |
| EXPECT_FALSE(less(xx, x)); |
| EXPECT_TRUE(less(x, y)); |
| EXPECT_FALSE(less(y, x)); |
| } |
| |
| // Tests of utility functions. |
| |
| struct ForEachInSetDifferenceTestParam { |
| std::vector<size_t> lhs; |
| std::vector<size_t> rhs; |
| std::vector<size_t> diff; |
| size_t expected_comparisons; |
| }; |
| |
| class ForEachInSetDifferenceTest |
| : public ::testing::Test, |
| public ::testing::WithParamInterface<ForEachInSetDifferenceTestParam> { |
| public: |
| // A wrapper of a size_t that counts its calls to operator==(). |
| class Dummy { |
| public: |
| size_t val = 0; |
| raw_ptr<size_t> num_equals_calls = nullptr; |
| }; |
| |
| std::vector<Dummy> ToDummies(const std::vector<size_t>& vec) { |
| std::vector<Dummy> out; |
| for (const size_t v : vec) |
| out.push_back({.val = v, .num_equals_calls = &num_equals_calls_}); |
| return out; |
| } |
| |
| size_t num_equals_calls_ = 0; |
| }; |
| |
| bool operator==(ForEachInSetDifferenceTest::Dummy x, |
| ForEachInSetDifferenceTest::Dummy y) { |
| CHECK(x.num_equals_calls && x.num_equals_calls == y.num_equals_calls); |
| ++*x.num_equals_calls; |
| return x.val == y.val; |
| } |
| |
| // Tests that for_each_in_set_difference() calls the callback for the expected |
| // elements and checks its number of comparisons. |
| TEST_P(ForEachInSetDifferenceTest, Test) { |
| std::vector<size_t> diff; |
| for_each_in_set_difference(ToDummies(GetParam().lhs), |
| ToDummies(GetParam().rhs), |
| [&diff](Dummy d) { diff.push_back(d.val); }); |
| EXPECT_THAT(diff, ElementsAreArray(GetParam().diff)); |
| EXPECT_EQ(num_equals_calls_, GetParam().expected_comparisons); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FormForestTest, |
| ForEachInSetDifferenceTest, |
| testing::Values( |
| ForEachInSetDifferenceTestParam{{}, {}, {}, 0}, |
| ForEachInSetDifferenceTestParam{{}, {1, 2, 3}, {}, 0}, |
| ForEachInSetDifferenceTestParam{{1}, {1, 2, 3, 4}, {}, 1}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3}, {1, 2, 3}, {}, 3}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3}, {1, 2, 3, 4, 5}, {}, 3}, |
| ForEachInSetDifferenceTestParam{{3, 4, 1, 2}, {1, 2, 3, 4}, {}, 6}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3, 4}, {1, 2, 3}, {4}, 6}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3, 4}, {1, 3, 4}, {2}, 6}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3, 4}, {4, 3, 2, 1}, {}, 13}, |
| ForEachInSetDifferenceTestParam{{3, 4, 1, 2}, {1, 2, 3}, {4}, 8}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3, 4}, {1}, {2, 3, 4}, 4}, |
| ForEachInSetDifferenceTestParam{{1, 2, 3, 4}, {}, {1, 2, 3, 4}, 0})); |
| |
| } // namespace |
| } // namespace autofill::internal |