| // Copyright 2013 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/autofill/content/renderer/form_cache.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/check_deref.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "components/autofill/content/renderer/autofill_agent.h" |
| #include "components/autofill/content/renderer/form_autofill_util.h" |
| #include "components/autofill/content/renderer/timing.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_form_control_element.h" |
| #include "third_party/blink/public/web/web_form_element.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // Determines whether the form is interesting enough to be sent to the browser |
| // for further operations. This is the case if any of the below holds: |
| // (1) At least one form field is not-checkable. (See crbug.com/1489075.) |
| // (2) At least one field has a non-empty autocomplete attribute. |
| // (3) There is at least one iframe. |
| // TODO(crbug.com/40283901): Remove check for radio buttons and checkboxes when |
| // we they're not extracted anymore. |
| bool IsFormInteresting(const FormData& form) { |
| auto is_checkable = [](FormControlType type) { |
| return type == FormControlType::kInputCheckbox || |
| type == FormControlType::kInputRadio; |
| }; |
| return !form.child_frames().empty() || |
| std::ranges::any_of(form.fields(), std::not_fn(is_checkable), |
| &FormFieldData::form_control_type) || |
| std::ranges::any_of(form.fields(), std::not_fn(&std::string::empty), |
| &FormFieldData::autocomplete_attribute); |
| } |
| |
| } // namespace |
| |
| FormCache::UpdateFormCacheResult::UpdateFormCacheResult() = default; |
| FormCache::UpdateFormCacheResult::UpdateFormCacheResult( |
| UpdateFormCacheResult&&) = default; |
| FormCache::UpdateFormCacheResult& FormCache::UpdateFormCacheResult::operator=( |
| UpdateFormCacheResult&&) = default; |
| FormCache::UpdateFormCacheResult::~UpdateFormCacheResult() = default; |
| |
| FormCache::FormCache(AutofillAgent* owner) : agent_(CHECK_DEREF(owner)) {} |
| FormCache::~FormCache() = default; |
| |
| void FormCache::Reset() { |
| extracted_forms_.clear(); |
| } |
| |
| FormCache::UpdateFormCacheResult FormCache::UpdateFormCache( |
| const FieldDataManager& field_data_manager, |
| const CallTimerState& timer_state) { |
| constexpr auto kUpdateFormCache = CallTimerState::CallSite::kUpdateFormCache; |
| ScopedCallTimer timer("UpdateFormCache", timer_state); |
| |
| // |extracted_forms_| is re-populated below in ProcessForm(). |
| std::map<FormRendererId, std::unique_ptr<FormData>> old_extracted_forms = |
| std::move(extracted_forms_); |
| extracted_forms_.clear(); |
| |
| UpdateFormCacheResult r; |
| r.removed_forms = base::MakeFlatSet<FormRendererId>( |
| old_extracted_forms, {}, |
| &std::pair<const FormRendererId, std::unique_ptr<FormData>>::first); |
| |
| for (const auto& [id, form] : old_extracted_forms) { |
| if (!form) { |
| r.removed_forms.erase(id); |
| } |
| } |
| |
| size_t num_fields_seen = 0; |
| size_t num_frames_seen = 0; |
| |
| // Helper function that stores new autofillable forms in |forms|. Returns |
| // false iff the total number of fields exceeds |kMaxExtractableFields|. |
| // Clears |form|'s FormData::child_frames if the total number of frames |
| // exceeds |kMaxExtractableChildFrames|. |
| auto ProcessForm = [&](FormData form) { |
| num_fields_seen += form.fields().size(); |
| num_frames_seen += form.child_frames().size(); |
| |
| // Enforce the kMaxExtractableFields limit: ignore all forms after this |
| // limit has been reached (i.e., abort parsing). |
| if (num_fields_seen > kMaxExtractableFields) { |
| return false; |
| } |
| |
| // Enforce the kMaxExtractableChildFrames limit: ignore the iframes, but |
| // do not ignore the fields (i.e., continue parsing). |
| if (num_frames_seen > kMaxExtractableChildFrames) { |
| form.set_child_frames({}); |
| } |
| |
| // Store only forms that contain iframes or fields. |
| if (IsFormInteresting(form)) { |
| FormRendererId form_id = form.renderer_id(); |
| auto it = old_extracted_forms.find(form_id); |
| if (it == old_extracted_forms.end() || !it->second || |
| !FormData::DeepEqual(*it->second, form)) { |
| r.updated_forms.push_back(form); |
| } |
| r.removed_forms.erase(form_id); |
| extracted_forms_[form_id] = std::make_unique<FormData>(std::move(form)); |
| } |
| return true; |
| }; |
| |
| blink::WebDocument document = agent_->GetDocument(); |
| if (!document) { |
| return r; |
| } |
| std::vector<blink::WebFormElement> form_elements = |
| document.GetTopLevelForms(); |
| // Add a null WebFormElement to account for the form of unowned elements. |
| form_elements.emplace_back(); |
| |
| bool stop_extracting_forms = false; |
| for (const blink::WebFormElement& form_element : form_elements) { |
| extracted_forms_[form_util::GetFormRendererId(form_element)] = nullptr; |
| if (stop_extracting_forms) { |
| continue; |
| } |
| if (std::optional<FormData> form = form_util::ExtractFormData( |
| document, form_element, field_data_manager, |
| agent_->GetCallTimerState(kUpdateFormCache), |
| agent_->button_titles_cache())) { |
| if (!ProcessForm(std::move(*form))) { |
| stop_extracting_forms = true; |
| } |
| } |
| } |
| return r; |
| } |
| |
| } // namespace autofill |