File: form_data_android_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (380 lines) | stat: -rw-r--r-- 14,338 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/android_autofill/browser/form_data_android.h"

#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/test/bind.h"
#include "base/types/cxx23_to_underlying.h"
#include "components/android_autofill/browser/android_autofill_bridge_factory.h"
#include "components/android_autofill/browser/form_field_data_android.h"
#include "components/android_autofill/browser/mock_form_data_android_bridge.h"
#include "components/android_autofill/browser/mock_form_field_data_android_bridge.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_test_api.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/unique_ids.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace autofill {
namespace {

using ::autofill::test::DeepEqualsFormData;
using ::testing::_;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::MockFunction;
using ::testing::Pointwise;
using ::testing::SizeIs;

constexpr SessionId kSampleSessionId(123);

MATCHER(SimilarFieldAs, "") {
  // `std::get<0>(arg)` is a `std::unique_ptr<FormFieldDataAndroid>`, while
  // `std::get<1>(arg)` is a `FormFieldData`.
  return std::get<0>(arg) && std::get<0>(arg)->SimilarFieldAs(std::get<1>(arg));
}

FormFieldData CreateTestField(std::u16string name = u"SomeName") {
  static uint64_t renderer_id = 1;
  FormFieldData f;
  f.set_name(std::move(name));
  f.set_name_attribute(f.name());
  f.set_id_attribute(u"some_id");
  f.set_form_control_type(FormControlType::kInputText);
  f.set_check_status(FormFieldData::CheckStatus::kChecked);
  f.set_role(FormFieldData::RoleAttribute::kOther);
  f.set_is_focusable(true);
  f.set_renderer_id(FieldRendererId(renderer_id++));
  return f;
}

FormData CreateTestForm() {
  FormData f;
  f.set_name(u"FormName");
  f.set_name_attribute(f.name());
  f.set_id_attribute(u"form_id");
  f.set_url(GURL("https://foo.com"));
  f.set_action(GURL("https://bar.com"));
  f.set_renderer_id(test::MakeFormRendererId());
  return f;
}

class FormDataAndroidTest : public ::testing::Test {
 public:
  FormDataAndroidTest() = default;
  ~FormDataAndroidTest() override = default;

  void SetUp() override {
    // Registers a testing factory for `FormDataAndroidBridge` that creates a
    // mocked bridge and always writes the pointer to the last created bridge
    // into `form_bridge_`.
    AndroidAutofillBridgeFactory::GetInstance()
        .SetFormDataAndroidTestingFactory(base::BindLambdaForTesting(
            [this]() -> std::unique_ptr<FormDataAndroidBridge> {
              auto bridge = std::make_unique<MockFormDataAndroidBridge>();
              form_bridge_ = bridge.get();
              return bridge;
            }));
    // Registers a testing factory for `FormFieldDataAndroidBridge` that creates
    // a mocked bridge and appends the pointers to the bridges to
    // `field_bridges_`.
    AndroidAutofillBridgeFactory::GetInstance()
        .SetFormFieldDataAndroidTestingFactory(base::BindLambdaForTesting(
            [this]() -> std::unique_ptr<FormFieldDataAndroidBridge> {
              auto bridge = std::make_unique<MockFormFieldDataAndroidBridge>();
              field_bridges_.push_back(bridge.get());
              return bridge;
            }));
  }

  void TearDown() override {
    form_bridge_ = nullptr;
    field_bridges_.clear();
    AndroidAutofillBridgeFactory::GetInstance()
        .SetFormDataAndroidTestingFactory({});
    AndroidAutofillBridgeFactory::GetInstance()
        .SetFormFieldDataAndroidTestingFactory({});
  }

 protected:
  const std::vector<MockFormFieldDataAndroidBridge*>& field_bridges() {
    return field_bridges_;
  }
  MockFormDataAndroidBridge& form_bridge() { return *form_bridge_; }

 private:
  test::AutofillUnitTestEnvironment autofill_test_environment_;
  std::vector<MockFormFieldDataAndroidBridge*> field_bridges_;
  raw_ptr<MockFormDataAndroidBridge> form_bridge_;
};

// Tests that `FormDataAndroid` creates a copy of its argument.
TEST_F(FormDataAndroidTest, Form) {
  FormData form = CreateTestForm();
  FormDataAndroid form_android(form, kSampleSessionId);

  EXPECT_TRUE(FormData::DeepEqual(form, form_android.form()));

  form.set_name(form.name() + u"x");
  EXPECT_FALSE(FormData::DeepEqual(form, form_android.form()));
}

// Tests that form similarity checks include name, name_attribute, id_attribute,
// url, and action.
// Similarity checks are used to determine whether a web page has modified a
// field significantly enough to warrant restarting an ongoing Autofill session,
// e.g., because their change would lead to a change in type predictions. As a
// result, this check includes attributes that the user cannot change and that
// are unlikely to have been superficial dynamic changes by Javascript on the
// website.
TEST_F(FormDataAndroidTest, SimilarFormAs) {
  FormData f = CreateTestForm();
  FormDataAndroid af(f, kSampleSessionId);

  // If forms are the same, they are similar.
  EXPECT_TRUE(af.SimilarFormAs(f));

  // If names differ, they are not similar.
  f.set_name(af.form().name() + u"x");
  EXPECT_FALSE(af.SimilarFormAs(f));

  // If name attributes differ, they are not similar.
  f = af.form();
  f.set_name_attribute(af.form().name_attribute() + u"x");
  EXPECT_FALSE(af.SimilarFormAs(f));

  // If id attributes differ, they are not similar.
  f = af.form();
  f.set_id_attribute(af.form().id_attribute() + u"x");
  EXPECT_FALSE(af.SimilarFormAs(f));

  // If urls differ, they are not similar.
  f = af.form();
  f.set_url(GURL("https://other.com"));
  EXPECT_FALSE(af.SimilarFormAs(f));

  // If actions differ, they are not similar.
  f = af.form();
  f.set_action(GURL("https://other.com"));
  EXPECT_FALSE(af.SimilarFormAs(f));

  // If their global ids differ, they are not similar.
  f = af.form();
  f.set_renderer_id(FormRendererId(f.renderer_id().value() + 1));
  EXPECT_FALSE(af.SimilarFormAs(f));
}

// Tests that form similarity checks similarity of the fields.
TEST_F(FormDataAndroidTest, SimilarFormAs_Fields) {
  FormData f = CreateTestForm();
  f.set_fields({CreateTestField()});
  FormDataAndroid af(f, kSampleSessionId);

  EXPECT_TRUE(af.SimilarFormAs(f));

  // Forms with different numbers of fields are not similar.
  f.set_fields({CreateTestField(), CreateTestField()});
  EXPECT_FALSE(af.SimilarFormAs(f));

  // Forms with similar fields are similar.
  f = af.form();
  test_api(f).field(0).set_value(f.fields().front().value() + u"x");
  EXPECT_TRUE(af.SimilarFormAs(f));

  // Forms with fields that are not similar, are not similar either.
  f = af.form();
  test_api(f).field(0).set_name(f.fields().front().name() + u"x");
  EXPECT_FALSE(af.SimilarFormAs(f));
}

TEST_F(FormDataAndroidTest, GetFieldIndex) {
  FormData f = CreateTestForm();
  f.set_fields({CreateTestField(u"name1"), CreateTestField(u"name2")});
  FormDataAndroid af(f, kSampleSessionId);

  size_t index = 100;
  EXPECT_TRUE(af.GetFieldIndex(f.fields()[1], &index));
  EXPECT_EQ(index, 1u);

  // As updates in `f` are not propagated to the Android version `af`, the
  // lookup fails.
  test_api(f).field(1).set_name(u"name3");
  EXPECT_FALSE(af.GetFieldIndex(f.fields()[1], &index));
}

// Tests that `GetSimilarFieldIndex` only checks field similarity.
TEST_F(FormDataAndroidTest, GetSimilarFieldIndex) {
  FormData f = CreateTestForm();
  f.set_fields({CreateTestField(u"name1"), CreateTestField(u"name2")});
  FormDataAndroid af(f, kSampleSessionId);

  size_t index = 100;
  // Value is not part of a field similarity check, so this field is similar to
  // af.form().fields[1].
  test_api(f).field(1).set_value(u"some value");
  EXPECT_TRUE(af.GetSimilarFieldIndex(f.fields()[1], &index));
  EXPECT_EQ(index, 1u);

  // Name is a part of the field similarity check, so there is no field similar
  // to this one.
  test_api(f).field(1).set_name(u"name3");
  EXPECT_FALSE(af.GetSimilarFieldIndex(f.fields()[1], &index));
}

// Tests that calling `OnFormFieldDidChange` propagates the changes to the
// affected field.
TEST_F(FormDataAndroidTest, OnFormFieldDidChange) {
  FormData form = CreateTestForm();
  form.set_fields({CreateTestField(), CreateTestField()});
  FormDataAndroid form_android(form, kSampleSessionId);

  ASSERT_THAT(field_bridges(), SizeIs(2));
  ASSERT_TRUE(field_bridges()[0]);
  ASSERT_TRUE(field_bridges()[1]);

  constexpr std::u16string_view kNewValue = u"SomeNewValue";
  EXPECT_CALL(*field_bridges()[0], UpdateValue).Times(0);
  EXPECT_CALL(*field_bridges()[1], UpdateValue(kNewValue));
  form_android.OnFormFieldDidChange(1, kNewValue);
  EXPECT_EQ(form_android.form().fields()[1].value(), kNewValue);
}

// Tests that the calls to update field types are propagated to the fields.
TEST_F(FormDataAndroidTest, UpdateFieldTypes) {
  FormData form = CreateTestForm();
  form.set_fields({CreateTestField(), CreateTestField()});
  FormDataAndroid form_android(form, kSampleSessionId);

  ASSERT_THAT(field_bridges(), SizeIs(2));
  ASSERT_TRUE(field_bridges()[0]);
  ASSERT_TRUE(field_bridges()[1]);

  EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes);
  EXPECT_CALL(*field_bridges()[1], UpdateFieldTypes);
  form_android.UpdateFieldTypes(FormStructure(form));
}

// Tests that `UpdateFieldTypes(base::flat_map<FieldGlobalId, AutofillType))`
// - sets all types (heuristic, server, computed),
// - only calls the JNI bridge for fields whose types differ.
TEST_F(FormDataAndroidTest, UpdateFieldTypesWithExplicitType) {
  const AutofillType kUsername(FieldType::USERNAME);
  const AutofillType kPassword(FieldType::PASSWORD);

  FormData form = CreateTestForm();
  form.set_fields({CreateTestField(), CreateTestField()});
  FormDataAndroid form_android(form, kSampleSessionId);
  ASSERT_THAT(field_bridges(), SizeIs(2));

  MockFunction<void(int)> check;
  {
    InSequence s;
    EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes(Eq(USERNAME)));
    EXPECT_CALL(*field_bridges()[1], UpdateFieldTypes(Eq(PASSWORD)));
    EXPECT_CALL(check, Call(1));
    EXPECT_CALL(check, Call(2));
    EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes(Eq(PASSWORD)));
    EXPECT_CALL(check, Call(3));
    EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes(Eq(USERNAME)));
  }

  // Update all the fields to new types.
  form_android.UpdateFieldTypes({{form.fields()[0].global_id(), USERNAME},
                                 {form.fields()[1].global_id(), PASSWORD}});
  check.Call(1);

  // Update to the same type - this should not trigger calls to JNI.
  form_android.UpdateFieldTypes({{form.fields()[0].global_id(), USERNAME},
                                 {form.fields()[1].global_id(), PASSWORD}});
  check.Call(2);

  // Update only one field.
  FieldGlobalId unknown_id = CreateTestField().global_id();
  form_android.UpdateFieldTypes(
      {{form.fields()[0].global_id(), PASSWORD}, {unknown_id, USERNAME}});
  check.Call(3);

  // Update both, but only the first one has changes.
  form_android.UpdateFieldTypes({{form.fields()[0].global_id(), USERNAME},
                                 {form.fields()[1].global_id(), PASSWORD}});
}

// Tests that the calls to update field types are propagated to the fields.
TEST_F(FormDataAndroidTest, UpdateFieldTypes_ChangedForm) {
  FormData form = CreateTestForm();
  form.set_fields({CreateTestField(), CreateTestField()});
  FormStructure form_structure(form);
  ASSERT_EQ(form_structure.field_count(), 2u);

  form.set_fields({CreateTestField(), form.fields()[1], form.fields()[0]});

  FormDataAndroid form_android(form, kSampleSessionId);

  ASSERT_THAT(field_bridges(), SizeIs(3));
  ASSERT_TRUE(field_bridges()[0]);
  ASSERT_TRUE(field_bridges()[1]);
  ASSERT_TRUE(field_bridges()[2]);

  EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes).Times(0);
  EXPECT_CALL(*field_bridges()[1], UpdateFieldTypes);
  EXPECT_CALL(*field_bridges()[2], UpdateFieldTypes);
  form_android.UpdateFieldTypes(form_structure);
}

// Tests that calling `UpdateFieldVisibilities` propagates the visibility to the
// affected fields and returns their indices.
TEST_F(FormDataAndroidTest, UpdateFieldVisibilities) {
  FormData form = CreateTestForm();
  form.set_fields({CreateTestField(), CreateTestField(), CreateTestField()});
  test_api(form).field(0).set_role(FormFieldData::RoleAttribute::kPresentation);
  test_api(form).field(1).set_is_focusable(false);
  EXPECT_FALSE(form.fields()[0].IsFocusable());
  EXPECT_FALSE(form.fields()[1].IsFocusable());
  EXPECT_TRUE(form.fields()[2].IsFocusable());
  FormDataAndroid form_android(form, kSampleSessionId);

  ASSERT_THAT(field_bridges(), SizeIs(3));
  ASSERT_TRUE(field_bridges()[0]);
  ASSERT_TRUE(field_bridges()[1]);
  ASSERT_TRUE(field_bridges()[2]);

  // `form_android` created a copy of `form` - therefore modifying the fields
  // here does not change the values inside `form_android`.
  test_api(form).field(0).set_role(FormFieldData::RoleAttribute::kOther);
  test_api(form).field(1).set_is_focusable(true);
  EXPECT_TRUE(form.fields()[0].IsFocusable());
  EXPECT_TRUE(form.fields()[1].IsFocusable());
  EXPECT_TRUE(form.fields()[2].IsFocusable());

  EXPECT_CALL(*field_bridges()[0], UpdateVisible(true));
  EXPECT_CALL(*field_bridges()[1], UpdateVisible(true));
  EXPECT_CALL(*field_bridges()[2], UpdateVisible).Times(0);
  form_android.UpdateFieldVisibilities(form);

  EXPECT_TRUE(FormData::DeepEqual(form, form_android.form()));
}

// Tests that `GetJavaPeer` passes the correct `FormData`, `SessionId` and
// `FormFieldDataAndroid` parameters to the Java bridge.
TEST_F(FormDataAndroidTest, GetJavaPeer) {
  FormData form = CreateTestForm();
  FormDataAndroid af(form, kSampleSessionId);
  EXPECT_CALL(form_bridge(),
              GetOrCreateJavaPeer(DeepEqualsFormData(form), kSampleSessionId,
                                  Pointwise(SimilarFieldAs(), form.fields())));
  af.GetJavaPeer();
}

}  // namespace
}  // namespace autofill