| // 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/autofill_agent.h" |
| |
| #include <stddef.h> |
| |
| #include <tuple> |
| |
| #include "base/command_line.h" |
| #include "base/containers/cxx20_erase_set.h" |
| #include "base/debug/alias.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/i18n/case_conversion.h" |
| #include "base/location.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "components/autofill/content/renderer/form_autofill_util.h" |
| #include "components/autofill/content/renderer/form_tracker.h" |
| #include "components/autofill/content/renderer/password_autofill_agent.h" |
| #include "components/autofill/content/renderer/password_generation_agent.h" |
| #include "components/autofill/content/renderer/renderer_save_password_progress_logger.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_switches.h" |
| #include "components/autofill/core/common/autofill_tick_clock.h" |
| #include "components/autofill/core/common/autofill_util.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/form_data_predictions.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/password_form_fill_data.h" |
| #include "components/autofill/core/common/save_password_progress_logger.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/origin_util.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "net/cert/cert_status_flags.h" |
| #include "services/service_manager/public/cpp/interface_provider.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/input/web_keyboard_event.h" |
| #include "third_party/blink/public/platform/web_url_request.h" |
| #include "third_party/blink/public/web/web_ax_enums.h" |
| #include "third_party/blink/public/web/web_ax_object.h" |
| #include "third_party/blink/public/web/web_console_message.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_element_collection.h" |
| #include "third_party/blink/public/web/web_form_control_element.h" |
| #include "third_party/blink/public/web/web_form_element.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_node.h" |
| #include "third_party/blink/public/web/web_option_element.h" |
| #include "third_party/blink/public/web/web_view.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| |
| using blink::WebAutofillClient; |
| using blink::WebAutofillState; |
| using blink::WebAXObject; |
| using blink::WebConsoleMessage; |
| using blink::WebDocument; |
| using blink::WebElement; |
| using blink::WebElementCollection; |
| using blink::WebFormControlElement; |
| using blink::WebFormElement; |
| using blink::WebFrame; |
| using blink::WebInputElement; |
| using blink::WebKeyboardEvent; |
| using blink::WebLocalFrame; |
| using blink::WebNode; |
| using blink::WebOptionElement; |
| using blink::WebString; |
| using blink::WebVector; |
| |
| namespace autofill { |
| |
| using form_util::ExtractMask; |
| using form_util::FindFormAndFieldForFormControlElement; |
| using form_util::IsElementEditable; |
| using form_util::IsOwnedByFrame; |
| using mojom::SubmissionSource; |
| using ShowAll = PasswordAutofillAgent::ShowAll; |
| using GenerationShowing = PasswordAutofillAgent::GenerationShowing; |
| using mojom::FocusedFieldType; |
| |
| namespace { |
| |
| // Time to wait in ms to ensure that only a single select or datalist change |
| // will be acted upon, instead of multiple in close succession (debounce time). |
| size_t kWaitTimeForOptionsChangesMs = 50; |
| |
| // Helper function to return EXTRACT_DATALIST if kAutofillExtractAllDatalist is |
| // enabled, otherwise EXTRACT_NONE is returned. |
| ExtractMask GetExtractDatalistMask() { |
| return base::FeatureList::IsEnabled(features::kAutofillExtractAllDatalists) |
| ? form_util::EXTRACT_DATALIST |
| : form_util::EXTRACT_NONE; |
| } |
| |
| } // namespace |
| |
| // During prerendering, we do not want the renderer to send messages to the |
| // corresponding driver. Since we use a channel associated interface, we still |
| // need to set up the mojo connection as before (i.e., we can't defer binding |
| // the interface). Instead, we enqueue our messages here as post-activation |
| // tasks. See post-prerendering activation steps here: |
| // https://wicg.github.io/nav-speculation/prerendering.html#prerendering-bcs-subsection |
| class AutofillAgent::DeferringAutofillDriver : public mojom::AutofillDriver { |
| public: |
| explicit DeferringAutofillDriver(AutofillAgent* agent) : agent_(agent) {} |
| ~DeferringAutofillDriver() override = default; |
| |
| private: |
| template <typename F, typename... Args> |
| void SendMsg(F fn, Args&&... args) { |
| DCHECK(!agent_->IsPrerendering()); |
| mojom::AutofillDriver& autofill_driver = agent_->GetAutofillDriver(); |
| DCHECK_NE(&autofill_driver, this); |
| (autofill_driver.*fn)(std::forward<Args>(args)...); |
| } |
| template <typename F, typename... Args> |
| void DeferMsg(F fn, Args... args) { |
| DCHECK(agent_->IsPrerendering()); |
| agent_->render_frame() |
| ->GetWebFrame() |
| ->GetDocument() |
| .AddPostPrerenderingActivationStep(base::BindOnce( |
| &DeferringAutofillDriver::SendMsg<F, Args...>, |
| weak_ptr_factory_.GetWeakPtr(), fn, std::forward<Args>(args)...)); |
| } |
| void SetFormToBeProbablySubmitted( |
| const absl::optional<FormData>& form) override { |
| DeferMsg(&mojom::AutofillDriver::SetFormToBeProbablySubmitted, form); |
| } |
| void FormsSeen(const std::vector<FormData>& updated_forms, |
| const std::vector<FormRendererId>& removed_forms) override { |
| DeferMsg(&mojom::AutofillDriver::FormsSeen, updated_forms, removed_forms); |
| } |
| void FormSubmitted(const FormData& form, |
| bool known_success, |
| mojom::SubmissionSource source) override { |
| DeferMsg(&mojom::AutofillDriver::FormSubmitted, form, known_success, |
| source); |
| } |
| void TextFieldDidChange(const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box, |
| base::TimeTicks timestamp) override { |
| DeferMsg(&mojom::AutofillDriver::TextFieldDidChange, form, field, |
| bounding_box, timestamp); |
| } |
| void TextFieldDidScroll(const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box) override { |
| DeferMsg(&mojom::AutofillDriver::TextFieldDidScroll, form, field, |
| bounding_box); |
| } |
| void SelectControlDidChange(const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box) override { |
| DeferMsg(&mojom::AutofillDriver::SelectControlDidChange, form, field, |
| bounding_box); |
| } |
| void SelectFieldOptionsDidChange(const FormData& form) override { |
| DeferMsg(&mojom::AutofillDriver::SelectFieldOptionsDidChange, form); |
| } |
| void AskForValuesToFill( |
| const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box, |
| AutoselectFirstSuggestion autoselect_first_suggestion, |
| FormElementWasClicked form_element_was_clicked) override { |
| DeferMsg(&mojom::AutofillDriver::AskForValuesToFill, form, field, |
| bounding_box, autoselect_first_suggestion, |
| form_element_was_clicked); |
| } |
| void HidePopup() override { DeferMsg(&mojom::AutofillDriver::HidePopup); } |
| void FocusNoLongerOnForm(bool had_interacted_form) override { |
| DeferMsg(&mojom::AutofillDriver::FocusNoLongerOnForm, had_interacted_form); |
| } |
| void FocusOnFormField(const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box) override { |
| DeferMsg(&mojom::AutofillDriver::FocusOnFormField, form, field, |
| bounding_box); |
| } |
| void DidFillAutofillFormData(const FormData& form, |
| base::TimeTicks timestamp) override { |
| DeferMsg(&mojom::AutofillDriver::DidFillAutofillFormData, form, timestamp); |
| } |
| void DidPreviewAutofillFormData() override { |
| DeferMsg(&mojom::AutofillDriver::DidPreviewAutofillFormData); |
| } |
| void DidEndTextFieldEditing() override { |
| DeferMsg(&mojom::AutofillDriver::DidEndTextFieldEditing); |
| } |
| void JavaScriptChangedAutofilledValue( |
| const FormData& form, |
| const FormFieldData& field, |
| const std::u16string& old_value) override { |
| DeferMsg(&mojom::AutofillDriver::JavaScriptChangedAutofilledValue, form, |
| field, old_value); |
| } |
| |
| AutofillAgent* agent_ = nullptr; |
| base::WeakPtrFactory<DeferringAutofillDriver> weak_ptr_factory_{this}; |
| }; |
| |
| AutofillAgent::FocusStateNotifier::FocusStateNotifier(AutofillAgent* agent) |
| : agent_(agent) {} |
| |
| AutofillAgent::FocusStateNotifier::~FocusStateNotifier() = default; |
| |
| void AutofillAgent::FocusStateNotifier::FocusedInputChanged( |
| const WebNode& node) { |
| CHECK(!node.IsNull()); |
| |
| FocusedFieldType new_focused_field_type = FocusedFieldType::kUnknown; |
| FieldRendererId new_focused_field_id = FieldRendererId(); |
| if (auto form_control_element = node.DynamicTo<WebFormControlElement>(); |
| !form_control_element.IsNull()) { |
| new_focused_field_type = GetFieldType(form_control_element); |
| new_focused_field_id = form_util::GetFieldRendererId(form_control_element); |
| } |
| NotifyIfChanged(new_focused_field_type, new_focused_field_id); |
| } |
| |
| void AutofillAgent::FocusStateNotifier::ResetFocus() { |
| FieldRendererId new_focused_field_id = FieldRendererId(); |
| FocusedFieldType new_focused_field_type = FocusedFieldType::kUnknown; |
| NotifyIfChanged(new_focused_field_type, new_focused_field_id); |
| } |
| |
| FocusedFieldType AutofillAgent::FocusStateNotifier::GetFieldType( |
| const WebFormControlElement& node) { |
| if (form_util::IsTextAreaElement(node.To<WebFormControlElement>())) { |
| return FocusedFieldType::kFillableTextArea; |
| } |
| |
| WebInputElement input_element = node.DynamicTo<WebInputElement>(); |
| if (input_element.IsNull() || !input_element.IsTextField() || |
| !IsElementEditable(input_element)) { |
| return FocusedFieldType::kUnfillableElement; |
| } |
| |
| if (WebString type = input_element.FormControlType(); |
| !type.IsNull() && type.Utf8() == "search") { |
| return FocusedFieldType::kFillableSearchField; |
| } |
| if (input_element.IsPasswordFieldForAutofill()) { |
| return FocusedFieldType::kFillablePasswordField; |
| } |
| if (agent_->password_autofill_agent_->IsUsernameInputField(input_element)) { |
| return FocusedFieldType::kFillableUsernameField; |
| } |
| return FocusedFieldType::kFillableNonSearchField; |
| } |
| |
| void AutofillAgent::FocusStateNotifier::NotifyIfChanged( |
| mojom::FocusedFieldType new_focused_field_type, |
| FieldRendererId new_focused_field_id) { |
| // Forward the request if the focused field is different from the previous |
| // one. |
| if (focused_field_id_ == new_focused_field_id && |
| focused_field_type_ == new_focused_field_type) { |
| return; |
| } |
| |
| // TODO(crbug.com/1425166): Move FocusedInputChanged to AutofillDriver. |
| agent_->GetPasswordManagerDriver().FocusedInputChanged( |
| new_focused_field_id, new_focused_field_type); |
| |
| focused_field_type_ = new_focused_field_type; |
| focused_field_id_ = new_focused_field_id; |
| } |
| |
| AutofillAgent::AutofillAgent(content::RenderFrame* render_frame, |
| PasswordAutofillAgent* password_autofill_agent, |
| PasswordGenerationAgent* password_generation_agent, |
| blink::AssociatedInterfaceRegistry* registry) |
| : content::RenderFrameObserver(render_frame), |
| form_cache_(render_frame->GetWebFrame()), |
| password_autofill_agent_(password_autofill_agent), |
| password_generation_agent_(password_generation_agent), |
| query_node_autofill_state_(WebAutofillState::kNotFilled), |
| is_popup_possibly_visible_(false), |
| is_generation_popup_possibly_visible_(false), |
| is_user_gesture_required_(true), |
| is_secure_context_required_(false), |
| form_tracker_(render_frame), |
| field_data_manager_(password_autofill_agent->GetFieldDataManager()), |
| focus_state_notifier_(this) { |
| render_frame->GetWebFrame()->SetAutofillClient(this); |
| password_autofill_agent->SetAutofillAgent(this); |
| AddFormObserver(this); |
| registry->AddInterface<mojom::AutofillAgent>(base::BindRepeating( |
| &AutofillAgent::BindPendingReceiver, base::Unretained(this))); |
| } |
| |
| // The destructor is not guaranteed to be called. Destruction happens (only) |
| // through the OnDestruct() event, which posts a task to delete this object. |
| // The process may be killed before this deletion can happen. |
| AutofillAgent::~AutofillAgent() { |
| RemoveFormObserver(this); |
| } |
| |
| void AutofillAgent::BindPendingReceiver( |
| mojo::PendingAssociatedReceiver<mojom::AutofillAgent> pending_receiver) { |
| receiver_.Bind(std::move(pending_receiver)); |
| } |
| |
| void AutofillAgent::DidCommitProvisionalLoad(ui::PageTransition transition) { |
| // Navigation to a new page or a page refresh. |
| element_.Reset(); |
| |
| form_cache_.Reset(); |
| ResetLastInteractedElements(); |
| OnFormNoLongerSubmittable(); |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::DidDispatchDOMContentLoadedEvent() { |
| ProcessForms(); |
| } |
| |
| void AutofillAgent::DidChangeScrollOffset() { |
| if (element_.IsNull()) |
| return; |
| |
| if (!focus_requires_scroll_) { |
| // Post a task here since scroll offset may change during layout. |
| // (https://crbug.com/804886) |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| render_frame() |
| ->GetTaskRunner(blink::TaskType::kInternalUserInteraction) |
| ->PostTask(FROM_HERE, |
| base::BindOnce(&AutofillAgent::DidChangeScrollOffsetImpl, |
| weak_ptr_factory_.GetWeakPtr(), element_)); |
| } else { |
| HidePopup(); |
| } |
| } |
| |
| void AutofillAgent::DidChangeScrollOffsetImpl( |
| const WebFormControlElement& element) { |
| if (element != element_ || element.IsNull() || focus_requires_scroll_ || |
| !is_popup_possibly_visible_ || !element.Focused()) { |
| return; |
| } |
| |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| FormData form; |
| FormFieldData field; |
| if (FindFormAndFieldForFormControlElement( |
| element, field_data_manager_.get(), |
| static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | |
| GetExtractDatalistMask()), |
| &form, &field)) { |
| GetAutofillDriver().TextFieldDidScroll(form, field, field.bounds); |
| } |
| |
| // Ignore subsequent scroll offset changes. |
| HidePopup(); |
| } |
| |
| void AutofillAgent::FocusedElementChanged(const WebElement& element) { |
| HidePopup(); |
| |
| if (element.IsNull()) { |
| // Focus moved away from the last interacted form (if any) to somewhere else |
| // on the page. |
| GetAutofillDriver().FocusNoLongerOnForm(!last_interacted_form_.IsNull()); |
| return; |
| } |
| |
| const WebFormControlElement form_control_element = |
| element.DynamicTo<WebFormControlElement>(); |
| |
| bool focus_moved_to_new_form = false; |
| if (!last_interacted_form_.IsNull() && |
| (form_control_element.IsNull() || |
| last_interacted_form_ != form_control_element.Form())) { |
| // The focused element is not part of the last interacted form (could be |
| // in a different form). |
| GetAutofillDriver().FocusNoLongerOnForm(/*had_interacted_form=*/true); |
| focus_moved_to_new_form = true; |
| } |
| |
| // Calls HandleFocusChangeComplete() after notifying the focus is no longer on |
| // the previous form, then early return. No need to notify the newly focused |
| // element because that will be done by HandleFocusChangeComplete() which |
| // triggers FormControlElementClicked(). |
| // Refer to http://crbug.com/1105254 |
| if ((IsKeyboardAccessoryEnabled() || !focus_requires_scroll_) && |
| !element.IsNull() && |
| element.GetDocument().GetFrame()->HasTransientUserActivation()) { |
| focused_node_was_last_clicked_ = true; |
| HandleFocusChangeComplete(); |
| } |
| |
| if (focus_moved_to_new_form) |
| return; |
| |
| if (form_control_element.IsNull() || !form_control_element.IsEnabled() || |
| form_control_element.IsReadOnly() || |
| !form_util::IsTextAreaElementOrTextInput(form_control_element)) { |
| return; |
| } |
| |
| element_ = form_control_element; |
| |
| FormData form; |
| FormFieldData field; |
| if (FindFormAndFieldForFormControlElement( |
| element_, field_data_manager_.get(), |
| static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | |
| GetExtractDatalistMask()), |
| &form, &field)) { |
| GetAutofillDriver().FocusOnFormField(form, field, field.bounds); |
| } |
| } |
| |
| void AutofillAgent::OnDestruct() { |
| Shutdown(); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE, |
| this); |
| } |
| |
| void AutofillAgent::AccessibilityModeChanged(const ui::AXMode& mode) { |
| is_screen_reader_enabled_ = mode.has_mode(ui::AXMode::kScreenReader); |
| } |
| |
| void AutofillAgent::FireHostSubmitEvents(const WebFormElement& form, |
| bool known_success, |
| SubmissionSource source) { |
| DCHECK(IsOwnedByFrame(form, render_frame())); |
| |
| FormData form_data; |
| if (!form_util::ExtractFormData(form, *field_data_manager_.get(), &form_data)) |
| return; |
| |
| FireHostSubmitEvents(form_data, known_success, source); |
| } |
| |
| void AutofillAgent::FireHostSubmitEvents(const FormData& form_data, |
| bool known_success, |
| SubmissionSource source) { |
| // We don't want to fire duplicate submission event. |
| if (!base::FeatureList::IsEnabled( |
| features::kAutofillAllowDuplicateFormSubmissions) && |
| !submitted_forms_.insert(form_data.unique_renderer_id).second) { |
| return; |
| } |
| |
| GetAutofillDriver().FormSubmitted(form_data, known_success, source); |
| } |
| |
| void AutofillAgent::Shutdown() { |
| receiver_.reset(); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void AutofillAgent::TextFieldDidEndEditing(const WebInputElement& element) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| // Sometimes "blur" events are side effects of the password generation |
| // handling the page. They should not affect any UI in the browser. |
| if (password_generation_agent_ && |
| password_generation_agent_->ShouldIgnoreBlur()) { |
| return; |
| } |
| GetAutofillDriver().DidEndTextFieldEditing(); |
| focus_state_notifier_.ResetFocus(); |
| if (password_generation_agent_) |
| password_generation_agent_->DidEndTextFieldEditing(element); |
| |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::SetUserGestureRequired(bool required) { |
| form_tracker_.set_user_gesture_required(required); |
| } |
| |
| void AutofillAgent::TextFieldDidChange(const WebFormControlElement& element) { |
| form_tracker_.TextFieldDidChange(element); |
| } |
| |
| void AutofillAgent::OnTextFieldDidChange(const WebInputElement& element) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| if (password_generation_agent_ && |
| password_generation_agent_->TextDidChangeInTextField(element)) { |
| is_popup_possibly_visible_ = true; |
| return; |
| } |
| |
| if (password_autofill_agent_->TextDidChangeInTextField(element)) { |
| is_popup_possibly_visible_ = true; |
| element_ = element; |
| return; |
| } |
| |
| ShowSuggestions(element, {.requires_caret_at_end = true}); |
| |
| FormData form; |
| FormFieldData field; |
| if (FindFormAndFieldForFormControlElement( |
| element, field_data_manager_.get(), |
| static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | |
| GetExtractDatalistMask()), |
| &form, &field)) { |
| GetAutofillDriver().TextFieldDidChange(form, field, field.bounds, |
| AutofillTickClock::NowTicks()); |
| } |
| } |
| |
| void AutofillAgent::TextFieldDidReceiveKeyDown(const WebInputElement& element, |
| const WebKeyboardEvent& event) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| if (event.windows_key_code == ui::VKEY_DOWN || |
| event.windows_key_code == ui::VKEY_UP) { |
| ShowSuggestions(element, |
| {.autofill_on_empty_values = true, |
| .requires_caret_at_end = true, |
| .autoselect_first_suggestion = AutoselectFirstSuggestion( |
| ShouldAutoselectFirstSuggestionOnArrowDown())}); |
| } |
| } |
| |
| void AutofillAgent::OpenTextDataListChooser(const WebInputElement& element) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| ShowSuggestions(element, {.autofill_on_empty_values = true}); |
| } |
| |
| // Notifies the AutofillDriver about changes in the <datalist> options in |
| // batches. |
| // |
| // A batch ends if no event occurred for `kWaitTimeForOptionsChangesMs` |
| // milliseconds. For a given batch, the AutofillDriver is informed only about |
| // the last field. That is, if within one batch the options of different |
| // fields changed, all but one of these events will be lost. |
| void AutofillAgent::DataListOptionsChanged(const WebInputElement& element) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| if (element.GetDocument().IsNull() || !is_popup_possibly_visible_ || |
| !element.Focused()) { |
| return; |
| } |
| |
| if (datalist_option_change_batch_timer_.IsRunning()) |
| datalist_option_change_batch_timer_.AbandonAndStop(); |
| |
| datalist_option_change_batch_timer_.Start( |
| FROM_HERE, base::Milliseconds(kWaitTimeForOptionsChangesMs), |
| base::BindRepeating(&AutofillAgent::BatchDataListOptionChange, |
| weak_ptr_factory_.GetWeakPtr(), element)); |
| } |
| |
| void AutofillAgent::BatchDataListOptionChange( |
| const blink::WebFormControlElement& element) { |
| if (element.GetDocument().IsNull()) |
| return; |
| |
| OnProvisionallySaveForm(element.Form(), element, |
| ElementChangeSource::TEXTFIELD_CHANGED); |
| } |
| |
| void AutofillAgent::UserGestureObserved() { |
| password_autofill_agent_->UserGestureObserved(); |
| } |
| |
| void AutofillAgent::TriggerRefillIfNeeded(const FormData& form) { |
| WebFormElement updated_form_element = form_util::FindFormByUniqueRendererId( |
| render_frame()->GetWebFrame()->GetDocument(), form.unique_renderer_id); |
| FormData updated_form_data; |
| if (updated_form_element.IsNull()) { |
| CollectFormlessElements(&updated_form_data); |
| } else { |
| form_util::ExtractFormData(updated_form_element, *field_data_manager_.get(), |
| &updated_form_data); |
| } |
| // Deep-compare forms, but don't take into account the fields' values. |
| if (!FormData::DeepEqual(form, updated_form_data)) |
| GetAutofillDriver().FormsSeen({updated_form_data}, {}); |
| } |
| |
| // mojom::AutofillAgent: |
| void AutofillAgent::FillOrPreviewForm(const FormData& form, |
| mojom::RendererFormDataAction action) { |
| // If `element_` is null or not focused, Autofill was either triggered from |
| // another frame or the `element_` has been detached from the DOM or the focus |
| // was moved otherwise. |
| // If `element_` is from a different form than `form`, then Autofill was |
| // triggered from a different form in the same frame, and either this is a |
| // subframe and both forms should be filled, or focus has changed right after |
| // the user accepted the suggestions. |
| // |
| // In these cases, we set `element_` to some form field as if Autofill had |
| // been triggered from that field. This is necessary because currently |
| // AutofillAgent relies on `element_` in many places. |
| if (!form.fields.empty() && |
| (element_.IsNull() || !element_.Focused() || |
| form_util::GetFormRendererId(form_util::GetOwningForm(element_)) != |
| form.unique_renderer_id)) { |
| WebDocument document = render_frame()->GetWebFrame()->GetDocument(); |
| element_ = form_util::FindFormControlElementByUniqueRendererId( |
| document, form.fields.front().unique_renderer_id); |
| } |
| |
| if (element_.IsNull()) |
| return; |
| |
| if (action == mojom::RendererFormDataAction::kPreview) { |
| ClearPreviewedForm(); |
| |
| query_node_autofill_state_ = element_.GetAutofillState(); |
| previewed_elements_ = form_util::FillOrPreviewForm(form, element_, action); |
| |
| GetAutofillDriver().DidPreviewAutofillFormData(); |
| } else { |
| was_last_action_fill_ = true; |
| |
| query_node_autofill_state_ = element_.GetAutofillState(); |
| bool filled_some_fields = |
| !form_util::FillOrPreviewForm(form, element_, action).empty(); |
| |
| if (!element_.Form().IsNull()) { |
| UpdateLastInteractedForm(element_.Form()); |
| } else { |
| formless_elements_were_autofilled_ |= filled_some_fields; |
| } |
| |
| // TODO(crbug.com/1198811): Inform the BrowserAutofillManager about the |
| // fields that were actually filled. It's possible that the form has changed |
| // since the time filling was triggered. |
| GetAutofillDriver().DidFillAutofillFormData(form, |
| AutofillTickClock::NowTicks()); |
| |
| TriggerRefillIfNeeded(form); |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| } |
| |
| void AutofillAgent::FieldTypePredictionsAvailable( |
| const std::vector<FormDataPredictions>& forms) { |
| bool attach_predictions_to_dom = base::FeatureList::IsEnabled( |
| features::test::kAutofillShowTypePredictions); |
| for (const auto& form : forms) { |
| form_cache_.ShowPredictions(form, attach_predictions_to_dom); |
| } |
| } |
| |
| void AutofillAgent::ClearSection() { |
| if (element_.IsNull()) |
| return; |
| |
| form_cache_.ClearSectionWithElement(element_); |
| } |
| |
| void AutofillAgent::ClearPreviewedForm() { |
| // TODO(crbug.com/816533): It is very rare, but it looks like the |element_| |
| // can be null if a provisional load was committed immediately prior to |
| // clearing the previewed form. |
| if (element_.IsNull()) |
| return; |
| |
| if (password_autofill_agent_->DidClearAutofillSelection(element_)) |
| return; |
| |
| // |password_generation_agent_| can be null in android_webview & weblayer. |
| if (password_generation_agent_ && |
| base::FeatureList::IsEnabled( |
| password_manager::features::kPasswordGenerationPreviewOnHover) && |
| password_generation_agent_->DidClearGenerationSuggestion(element_)) { |
| return; |
| } |
| |
| form_util::ClearPreviewedElements(previewed_elements_, element_, |
| query_node_autofill_state_); |
| previewed_elements_ = {}; |
| } |
| |
| void AutofillAgent::FillFieldWithValue(FieldRendererId field_id, |
| const std::u16string& value) { |
| if (element_.IsNull() || |
| field_id != FieldRendererId(element_.UniqueRendererFormControlId())) { |
| return; |
| } |
| |
| if (form_util::IsTextAreaElementOrTextInput(element_)) |
| DoFillFieldWithValue(value, element_, WebAutofillState::kAutofilled); |
| } |
| |
| void AutofillAgent::PreviewFieldWithValue(FieldRendererId field_id, |
| const std::u16string& value) { |
| if (element_.IsNull() || |
| field_id != FieldRendererId(element_.UniqueRendererFormControlId())) { |
| return; |
| } |
| |
| WebInputElement input_element = element_.DynamicTo<WebInputElement>(); |
| if (!input_element.IsNull()) |
| DoPreviewFieldWithValue(value, input_element); |
| } |
| |
| void AutofillAgent::SetSuggestionAvailability( |
| FieldRendererId field_id, |
| const mojom::AutofillState state) { |
| if (element_.IsNull() || |
| field_id != FieldRendererId(element_.UniqueRendererFormControlId())) { |
| return; |
| } |
| |
| WebInputElement input_element = element_.DynamicTo<WebInputElement>(); |
| if (!input_element.IsNull()) { |
| switch (state) { |
| case mojom::AutofillState::kAutofillAvailable: |
| WebAXObject::FromWebNode(input_element) |
| .HandleAutofillStateChanged( |
| blink::WebAXAutofillState::kAutofillAvailable); |
| return; |
| case mojom::AutofillState::kAutocompleteAvailable: |
| WebAXObject::FromWebNode(input_element) |
| .HandleAutofillStateChanged( |
| blink::WebAXAutofillState::kAutocompleteAvailable); |
| return; |
| case mojom::AutofillState::kNoSuggestions: |
| WebAXObject::FromWebNode(input_element) |
| .HandleAutofillStateChanged( |
| blink::WebAXAutofillState::kNoSuggestions); |
| return; |
| } |
| NOTREACHED(); |
| } |
| } |
| |
| void AutofillAgent::AcceptDataListSuggestion( |
| FieldRendererId field_id, |
| const std::u16string& suggested_value) { |
| if (element_.IsNull() || |
| field_id != FieldRendererId(element_.UniqueRendererFormControlId())) { |
| return; |
| } |
| |
| WebInputElement input_element = element_.DynamicTo<WebInputElement>(); |
| if (input_element.IsNull()) { |
| // Early return for non-input fields such as textarea. |
| return; |
| } |
| std::u16string new_value = suggested_value; |
| // If this element takes multiple values then replace the last part with |
| // the suggestion. |
| if (input_element.IsMultiple() && input_element.IsEmailField()) { |
| std::u16string value = input_element.EditingValue().Utf16(); |
| std::vector<base::StringPiece16> parts = base::SplitStringPiece( |
| value, u",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (parts.size() == 0) |
| parts.push_back(base::StringPiece16()); |
| |
| std::u16string last_part(parts.back()); |
| // We want to keep just the leading whitespace. |
| for (size_t i = 0; i < last_part.size(); ++i) { |
| if (!base::IsUnicodeWhitespace(last_part[i])) { |
| last_part = last_part.substr(0, i); |
| break; |
| } |
| } |
| last_part.append(suggested_value); |
| parts.back() = last_part; |
| |
| new_value = base::JoinString(parts, u","); |
| } |
| DoFillFieldWithValue(new_value, element_, WebAutofillState::kNotFilled); |
| } |
| |
| void AutofillAgent::FillPasswordSuggestion(const std::u16string& username, |
| const std::u16string& password) { |
| if (element_.IsNull()) |
| return; |
| |
| bool handled = |
| password_autofill_agent_->FillSuggestion(element_, username, password); |
| DCHECK(handled); |
| } |
| |
| void AutofillAgent::PreviewPasswordSuggestion(const std::u16string& username, |
| const std::u16string& password) { |
| if (element_.IsNull()) |
| return; |
| |
| bool handled = password_autofill_agent_->PreviewSuggestion( |
| element_, blink::WebString::FromUTF16(username), |
| blink::WebString::FromUTF16(password)); |
| DCHECK(handled); |
| } |
| |
| void AutofillAgent::PreviewPasswordGenerationSuggestion( |
| const std::u16string& password) { |
| DCHECK(password_generation_agent_); |
| password_generation_agent_->PreviewGenerationSuggestion(password); |
| } |
| |
| bool AutofillAgent::CollectFormlessElements(FormData* output) const { |
| if (render_frame() == nullptr || render_frame()->GetWebFrame() == nullptr) |
| return false; |
| |
| WebDocument document = render_frame()->GetWebFrame()->GetDocument(); |
| |
| // Build up the FormData from the unowned elements. This logic mostly |
| // mirrors the construction of the synthetic form in form_cache.cc, but |
| // happens at submit-time so we can capture the modifications the user |
| // has made, and doesn't depend on form_cache's internal state. |
| std::vector<WebFormControlElement> control_elements = |
| form_util::GetUnownedAutofillableFormFieldElements(document); |
| |
| std::vector<WebElement> iframe_elements = |
| form_util::GetUnownedIframeElements(document); |
| |
| const ExtractMask extract_mask = static_cast<ExtractMask>( |
| form_util::EXTRACT_VALUE | form_util::EXTRACT_OPTIONS); |
| |
| return form_util::UnownedFormElementsToFormData( |
| control_elements, iframe_elements, nullptr, document, |
| field_data_manager_.get(), extract_mask, output, nullptr); |
| } |
| |
| void AutofillAgent::ShowSuggestions(const WebFormControlElement& element, |
| const ShowSuggestionsOptions& options) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| if (!element.IsEnabled() || element.IsReadOnly()) |
| return; |
| if (!element.SuggestedValue().IsEmpty()) |
| return; |
| |
| const WebInputElement input_element = element.DynamicTo<WebInputElement>(); |
| if (!input_element.IsNull()) { |
| if (!input_element.IsTextField()) |
| return; |
| if (!input_element.SuggestedValue().IsEmpty()) |
| return; |
| } else { |
| DCHECK(form_util::IsTextAreaElement(element)); |
| if (!element.To<WebFormControlElement>().SuggestedValue().IsEmpty()) |
| return; |
| } |
| |
| // Don't attempt to autofill with values that are too large or if filling |
| // criteria are not met. Keyboard Accessory may still be shown when the |
| // |value| is empty, do not attempt to hide it. |
| WebString value = element.EditingValue(); |
| if (value.length() > kMaxStringLength || |
| (!options.autofill_on_empty_values && value.IsEmpty() && |
| !IsKeyboardAccessoryEnabled()) || |
| (options.requires_caret_at_end && |
| (element.SelectionStart() != element.SelectionEnd() || |
| element.SelectionEnd() != static_cast<int>(value.length())))) { |
| // Any popup currently showing is obsolete. |
| HidePopup(); |
| return; |
| } |
| |
| element_ = element; |
| if (form_util::IsAutofillableInputElement(input_element) && |
| password_autofill_agent_->ShowSuggestions( |
| input_element, ShowAll(options.show_full_suggestion_list), |
| GenerationShowing(is_generation_popup_possibly_visible_))) { |
| is_popup_possibly_visible_ = true; |
| return; |
| } |
| |
| if (is_generation_popup_possibly_visible_) |
| return; |
| |
| // Password field elements should only have suggestions shown by the password |
| // autofill agent. |
| // The /*disable presubmit*/ comment below is used to disable a presubmit |
| // script that ensures that only IsPasswordFieldForAutofill() is used in this |
| // code (it has to appear between the function name and the parentesis to not |
| // match a regex). In this specific case we are actually interested in whether |
| // the field is currently a password field, not whether it has ever been a |
| // password field. |
| if (!input_element.IsNull() && |
| input_element.IsPasswordField /*disable presubmit*/ () && |
| !query_password_suggestion_) { |
| return; |
| } |
| |
| QueryAutofillSuggestions(element, options.autoselect_first_suggestion, |
| options.form_element_was_clicked); |
| } |
| |
| void AutofillAgent::SetQueryPasswordSuggestion(bool query) { |
| query_password_suggestion_ = query; |
| } |
| |
| void AutofillAgent::SetSecureContextRequired(bool required) { |
| is_secure_context_required_ = required; |
| } |
| |
| void AutofillAgent::SetFocusRequiresScroll(bool require) { |
| focus_requires_scroll_ = require; |
| } |
| |
| void AutofillAgent::EnableHeavyFormDataScraping() { |
| is_heavy_form_data_scraping_enabled_ = true; |
| } |
| |
| void AutofillAgent::SetFieldsEligibleForManualFilling( |
| const std::vector<FieldRendererId>& fields) { |
| form_cache_.SetFieldsEligibleForManualFilling(fields); |
| } |
| |
| void AutofillAgent::QueryAutofillSuggestions( |
| const WebFormControlElement& element, |
| AutoselectFirstSuggestion autoselect_first_suggestion, |
| FormElementWasClicked form_element_was_clicked) { |
| blink::WebLocalFrame* frame = element.GetDocument().GetFrame(); |
| if (!frame) |
| return; |
| |
| DCHECK(!element.DynamicTo<WebInputElement>().IsNull() || |
| form_util::IsTextAreaElement(element)); |
| |
| FormData form; |
| FormFieldData field; |
| if (!FindFormAndFieldForFormControlElement( |
| element, field_data_manager_.get(), |
| static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | |
| GetExtractDatalistMask()), |
| &form, &field)) { |
| // If we didn't find the cached form, at least let autocomplete have a shot |
| // at providing suggestions. |
| WebFormControlElementToFormField( |
| form_util::GetOwningForm(element), element, nullptr, |
| static_cast<ExtractMask>(form_util::EXTRACT_VALUE | |
| form_util::EXTRACT_BOUNDS | |
| GetExtractDatalistMask()), |
| &field); |
| } |
| |
| if (is_secure_context_required_ && |
| !(element.GetDocument().IsSecureContext())) { |
| LOG(WARNING) << "Autofill suggestions are disabled because the document " |
| "isn't a secure context."; |
| return; |
| } |
| |
| if (!base::FeatureList::IsEnabled(features::kAutofillExtractAllDatalists)) { |
| const WebInputElement input_element = element.DynamicTo<WebInputElement>(); |
| if (!input_element.IsNull()) { |
| // Find the datalist values and send them to the browser process. |
| form_util::GetDataListSuggestions(input_element, &field.datalist_values, |
| &field.datalist_labels); |
| } |
| } |
| |
| is_popup_possibly_visible_ = true; |
| GetAutofillDriver().AskForValuesToFill(form, field, field.bounds, |
| autoselect_first_suggestion, |
| form_element_was_clicked); |
| } |
| |
| void AutofillAgent::DoFillFieldWithValue(const std::u16string& value, |
| blink::WebFormControlElement& element, |
| WebAutofillState autofill_state) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| form_tracker_.set_ignore_control_changes(true); |
| |
| element.SetAutofillValue(blink::WebString::FromUTF16(value), autofill_state); |
| |
| WebInputElement input_element = element.DynamicTo<WebInputElement>(); |
| // `input_element` can be null for textarea elements. |
| if (!input_element.IsNull()) |
| password_autofill_agent_->UpdateStateForTextChange(input_element); |
| |
| form_tracker_.set_ignore_control_changes(false); |
| } |
| |
| void AutofillAgent::DoPreviewFieldWithValue(const std::u16string& value, |
| WebInputElement& node) { |
| DCHECK(IsOwnedByFrame(node, render_frame())); |
| |
| ClearPreviewedForm(); |
| query_node_autofill_state_ = element_.GetAutofillState(); |
| node.SetSuggestedValue(blink::WebString::FromUTF16(value)); |
| form_util::PreviewSuggestion(node.SuggestedValue().Utf16(), |
| node.Value().Utf16(), &node); |
| previewed_elements_.push_back(node); |
| } |
| |
| void AutofillAgent::TriggerReparse() { |
| if (!reparse_timer_.IsRunning()) { |
| reparse_timer_.Start(FROM_HERE, base::Milliseconds(100), |
| base::BindOnce(&AutofillAgent::ProcessForms, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void AutofillAgent::TriggerReparseWithResponse( |
| base::OnceCallback<void(bool)> callback) { |
| if (reparse_with_response_timer_.IsRunning()) { |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| reparse_with_response_timer_.Start( |
| FROM_HERE, base::Milliseconds(100), |
| base::BindOnce( |
| [](base::WeakPtr<AutofillAgent> self, |
| base::OnceCallback<void(bool)> callback) { |
| if (!self) { |
| return; |
| } |
| self->ProcessForms(); |
| std::move(callback).Run(/*success=*/true); |
| }, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void AutofillAgent::ProcessForms() { |
| FormCache::UpdateFormCacheResult cache = |
| form_cache_.UpdateFormCache(field_data_manager_.get()); |
| |
| if (!cache.updated_forms.empty() || !cache.removed_forms.empty()) { |
| GetAutofillDriver().FormsSeen(cache.updated_forms, |
| std::move(cache.removed_forms).extract()); |
| } |
| } |
| |
| void AutofillAgent::HidePopup() { |
| if (!is_popup_possibly_visible_) |
| return; |
| is_popup_possibly_visible_ = false; |
| is_generation_popup_possibly_visible_ = false; |
| |
| // The keyboard accessory has a separate, more complex hiding logic. |
| if (IsKeyboardAccessoryEnabled()) |
| return; |
| |
| GetAutofillDriver().HidePopup(); |
| } |
| |
| void AutofillAgent::DidAddOrRemoveFormRelatedElementsDynamically() { |
| // If the control flow is here than the document was at least loaded. The |
| // whole page doesn't have to be loaded. |
| ProcessForms(); |
| password_autofill_agent_->OnDynamicFormsSeen(); |
| } |
| |
| void AutofillAgent::DidCompleteFocusChangeInFrame() { |
| WebDocument doc = render_frame()->GetWebFrame()->GetDocument(); |
| WebElement focused_element; |
| if (!doc.IsNull()) |
| focused_element = doc.FocusedElement(); |
| |
| if (!focused_element.IsNull()) { |
| SendFocusedInputChangedNotificationToBrowser(focused_element); |
| } |
| |
| if (!IsKeyboardAccessoryEnabled() && focus_requires_scroll_) |
| HandleFocusChangeComplete(); |
| |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::DidReceiveLeftMouseDownOrGestureTapInNode( |
| const WebNode& node) { |
| DCHECK(!node.IsNull()); |
| focused_node_was_last_clicked_ = node.Focused(); |
| |
| #if defined(ANDROID) |
| HandleFocusChangeComplete(); |
| #else |
| if (!focus_requires_scroll_) { |
| HandleFocusChangeComplete(); |
| } |
| #endif |
| } |
| |
| void AutofillAgent::SelectControlDidChange( |
| const WebFormControlElement& element) { |
| form_tracker_.SelectControlDidChange(element); |
| } |
| |
| // Notifies the AutofillDriver about changes in the <select> options in batches. |
| // |
| // A batch ends if no event occurred for `kWaitTimeForOptionsChangesMs` |
| // milliseconds. For a given batch, the AutofillDriver is informed only about |
| // the last FormData. That is, if within one batch the options of different |
| // forms changed, all but one of these events will be lost. |
| void AutofillAgent::SelectFieldOptionsChanged( |
| const blink::WebFormControlElement& element) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| if (!was_last_action_fill_ || element_.IsNull()) |
| return; |
| |
| if (select_option_change_batch_timer_.IsRunning()) |
| select_option_change_batch_timer_.AbandonAndStop(); |
| |
| select_option_change_batch_timer_.Start( |
| FROM_HERE, base::Milliseconds(kWaitTimeForOptionsChangesMs), |
| base::BindRepeating(&AutofillAgent::BatchSelectOptionChange, |
| weak_ptr_factory_.GetWeakPtr(), element)); |
| } |
| |
| void AutofillAgent::BatchSelectOptionChange( |
| const blink::WebFormControlElement& element) { |
| if (element.GetDocument().IsNull()) |
| return; |
| |
| // Look for the form and field associated with the select element. If they are |
| // found, notify the driver that the form was modified dynamically. |
| FormData form; |
| FormFieldData field; |
| if (FindFormAndFieldForFormControlElement(element, field_data_manager_.get(), |
| &form, &field) && |
| !field.options.empty()) { |
| GetAutofillDriver().SelectFieldOptionsDidChange(form); |
| } |
| } |
| |
| bool AutofillAgent::ShouldSuppressKeyboard( |
| const WebFormControlElement& element) { |
| // Note: Consider supporting other autofill types in the future as well. |
| #if BUILDFLAG(IS_ANDROID) |
| if (password_autofill_agent_->ShouldSuppressKeyboard()) |
| return true; |
| #endif |
| return false; |
| } |
| |
| void AutofillAgent::FormElementReset(const WebFormElement& form) { |
| DCHECK(IsOwnedByFrame(form, render_frame())); |
| |
| password_autofill_agent_->InformAboutFormClearing(form); |
| } |
| |
| void AutofillAgent::PasswordFieldReset(const WebInputElement& element) { |
| DCHECK(IsOwnedByFrame(element, render_frame())); |
| |
| password_autofill_agent_->InformAboutFieldClearing(element); |
| } |
| |
| bool AutofillAgent::IsPrerendering() const { |
| return render_frame()->GetWebFrame()->GetDocument().IsPrerendering(); |
| } |
| |
| void AutofillAgent::FormControlElementClicked( |
| const WebFormControlElement& element) { |
| last_clicked_form_control_element_for_testing_ = |
| FieldRendererId(element.UniqueRendererFormControlId()); |
| was_last_action_fill_ = false; |
| |
| const WebInputElement input_element = element.DynamicTo<WebInputElement>(); |
| if (input_element.IsNull() && !form_util::IsTextAreaElement(element)) |
| return; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| password_autofill_agent_->TryToShowTouchToFill(element); |
| #endif |
| |
| ShowSuggestions( |
| element, {.autofill_on_empty_values = true, |
| // Even if the user has not edited an input element, it may |
| // still contain a value: A default value filled by the website. |
| // In that case, we don't want to elide suggestions that don't |
| // have a common prefix with the default value. |
| .show_full_suggestion_list = |
| element.IsAutofilled() || !element.UserHasEditedTheField(), |
| .form_element_was_clicked = FormElementWasClicked(true)}); |
| |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::HandleFocusChangeComplete() { |
| WebElement focused_element = |
| render_frame()->GetWebFrame()->GetDocument().FocusedElement(); |
| // When using Talkback on Android, and possibly others, traversing to and |
| // focusing a field will not register as a click. Thus, when screen readers |
| // are used, treat the focused node as if it was the last clicked. Also check |
| // to ensure focus is on a field where text can be entered. |
| // When the focus is on a non-input field on Android, keyboard accessory may |
| // be shown if autofill data is available. Make sure to hide the accessory if |
| // focus changes to another element. |
| if ((focused_node_was_last_clicked_ || is_screen_reader_enabled_) && |
| !focused_element.IsNull() && focused_element.IsFormControlElement()) { |
| WebFormControlElement focused_form_control_element = |
| focused_element.To<WebFormControlElement>(); |
| if (form_util::IsTextAreaElementOrTextInput(focused_form_control_element)) { |
| FormControlElementClicked(focused_form_control_element); |
| } else if (IsKeyboardAccessoryEnabled()) { |
| GetAutofillDriver().HidePopup(); |
| } |
| } else if (IsKeyboardAccessoryEnabled()) { |
| GetAutofillDriver().HidePopup(); |
| } |
| |
| focused_node_was_last_clicked_ = false; |
| |
| if (password_generation_agent_ && |
| password_generation_agent_->HandleFocusChangeComplete(focused_element)) { |
| is_generation_popup_possibly_visible_ = true; |
| is_popup_possibly_visible_ = true; |
| } |
| |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::SendFocusedInputChangedNotificationToBrowser( |
| const WebElement& node) { |
| focus_state_notifier_.FocusedInputChanged(node); |
| |
| auto input_element = node.DynamicTo<WebInputElement>(); |
| if (!input_element.IsNull()) { |
| field_data_manager_->UpdateFieldDataMapWithNullValue( |
| form_util::GetFieldRendererId(input_element), |
| FieldPropertiesFlags::kHadFocus); |
| } |
| } |
| |
| void AutofillAgent::AjaxSucceeded() { |
| form_tracker_.AjaxSucceeded(); |
| |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::JavaScriptChangedAutofilledValue( |
| const blink::WebFormControlElement& element, |
| const blink::WebString& old_value) { |
| if (old_value == element.Value()) |
| return; |
| FormData form; |
| FormFieldData field; |
| if (FindFormAndFieldForFormControlElement(element, field_data_manager_.get(), |
| &form, &field)) { |
| GetAutofillDriver().JavaScriptChangedAutofilledValue(form, field, |
| old_value.Utf16()); |
| } |
| } |
| |
| void AutofillAgent::OnProvisionallySaveForm( |
| const WebFormElement& form, |
| const WebFormControlElement& element, |
| ElementChangeSource source) { |
| if (source == ElementChangeSource::WILL_SEND_SUBMIT_EVENT) { |
| // Fire the form submission event to avoid missing submission when web site |
| // handles the onsubmit event, this also gets the form before Javascript |
| // could change it. |
| // We don't clear submitted_forms_ because OnFormSubmitted will normally be |
| // invoked afterwards and we don't want to fire the same event twice. |
| FireHostSubmitEvents(form, /*known_success=*/false, |
| SubmissionSource::FORM_SUBMISSION); |
| ResetLastInteractedElements(); |
| } else if (source == ElementChangeSource::TEXTFIELD_CHANGED || |
| source == ElementChangeSource::SELECT_CHANGED) { |
| // Remember the last form the user interacted with. |
| if (!element.Form().IsNull()) { |
| UpdateLastInteractedForm(element.Form()); |
| } else { |
| // Remove visible elements. |
| WebDocument doc = render_frame()->GetWebFrame()->GetDocument(); |
| if (!doc.IsNull()) { |
| base::EraseIf( |
| formless_elements_user_edited_, |
| [&doc](const FieldRendererId field_id) { |
| WebFormControlElement field = |
| form_util::FindFormControlElementByUniqueRendererId( |
| doc, field_id, /*form_to_be_searched =*/FormRendererId()); |
| return !field.IsNull() && |
| form_util::IsWebElementFocusableForAutofill(field); |
| }); |
| } |
| formless_elements_user_edited_.insert( |
| FieldRendererId(element.UniqueRendererFormControlId())); |
| provisionally_saved_form_ = absl::make_optional<FormData>(); |
| if (!CollectFormlessElements(&provisionally_saved_form_.value())) { |
| provisionally_saved_form_.reset(); |
| } else { |
| last_interacted_form_.Reset(); |
| } |
| } |
| |
| if (source == ElementChangeSource::TEXTFIELD_CHANGED) { |
| OnTextFieldDidChange(element.To<WebInputElement>()); |
| } else { |
| FormData form_data; |
| FormFieldData field; |
| if (FindFormAndFieldForFormControlElement( |
| element, field_data_manager_.get(), |
| static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | |
| GetExtractDatalistMask()), |
| &form_data, &field)) { |
| GetAutofillDriver().SelectControlDidChange(form_data, field, |
| field.bounds); |
| } |
| } |
| } |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::OnProbablyFormSubmitted() { |
| absl::optional<FormData> form_data = GetSubmittedForm(); |
| if (form_data.has_value()) { |
| FireHostSubmitEvents(form_data.value(), /*known_success=*/false, |
| SubmissionSource::PROBABLY_FORM_SUBMITTED); |
| } |
| ResetLastInteractedElements(); |
| OnFormNoLongerSubmittable(); |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::OnFormSubmitted(const WebFormElement& form) { |
| DCHECK(IsOwnedByFrame(form, render_frame())); |
| |
| // Fire the submission event here because WILL_SEND_SUBMIT_EVENT is skipped |
| // if javascript calls submit() directly. |
| FireHostSubmitEvents(form, /*known_success=*/false, |
| SubmissionSource::FORM_SUBMISSION); |
| ResetLastInteractedElements(); |
| OnFormNoLongerSubmittable(); |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::OnInferredFormSubmission(SubmissionSource source) { |
| if (source == SubmissionSource::FRAME_DETACHED && |
| render_frame()->GetWebFrame()->IsOutermostMainFrame()) { |
| // No op. |
| } else if (source == SubmissionSource::SAME_DOCUMENT_NAVIGATION && |
| !render_frame()->GetWebFrame()->IsOutermostMainFrame()) { |
| // No op. |
| } else if (source == SubmissionSource::FRAME_DETACHED) { |
| // Should not access the frame because it is now detached. Instead, use |
| // |provisionally_saved_form_|. |
| if (provisionally_saved_form_.has_value()) |
| FireHostSubmitEvents(provisionally_saved_form_.value(), |
| /*known_success=*/true, source); |
| } else { |
| absl::optional<FormData> form_data = GetSubmittedForm(); |
| if (form_data.has_value()) |
| FireHostSubmitEvents(form_data.value(), /*known_success=*/true, source); |
| } |
| ResetLastInteractedElements(); |
| OnFormNoLongerSubmittable(); |
| SendPotentiallySubmittedFormToBrowser(); |
| } |
| |
| void AutofillAgent::AddFormObserver(Observer* observer) { |
| form_tracker_.AddObserver(observer); |
| } |
| |
| void AutofillAgent::RemoveFormObserver(Observer* observer) { |
| form_tracker_.RemoveObserver(observer); |
| } |
| |
| void AutofillAgent::TrackAutofilledElement( |
| const blink::WebFormControlElement& element) { |
| form_tracker_.TrackAutofilledElement(element); |
| } |
| |
| absl::optional<FormData> AutofillAgent::GetSubmittedForm() const { |
| if (!last_interacted_form_.IsNull()) { |
| FormData form; |
| if (form_util::ExtractFormData(last_interacted_form_, |
| *field_data_manager_.get(), &form)) { |
| return absl::make_optional(form); |
| } else if (provisionally_saved_form_.has_value()) { |
| return absl::make_optional(provisionally_saved_form_.value()); |
| } |
| } else if (formless_elements_were_autofilled_ || |
| (formless_elements_user_edited_.size() != 0 && |
| !form_util::IsSomeControlElementVisible( |
| render_frame()->GetWebFrame(), |
| formless_elements_user_edited_))) { |
| // we check if all the elements the user has interacted with are gone, |
| // to decide if submission has occurred, and use the |
| // provisionally_saved_form_ saved in OnProvisionallySaveForm() if fail to |
| // construct form. |
| FormData form; |
| if (CollectFormlessElements(&form)) { |
| return absl::make_optional(form); |
| } else if (provisionally_saved_form_.has_value()) { |
| return absl::make_optional(provisionally_saved_form_.value()); |
| } |
| } |
| return absl::nullopt; |
| } |
| |
| void AutofillAgent::SendPotentiallySubmittedFormToBrowser() { |
| GetAutofillDriver().SetFormToBeProbablySubmitted(GetSubmittedForm()); |
| } |
| |
| void AutofillAgent::ResetLastInteractedElements() { |
| last_interacted_form_.Reset(); |
| last_clicked_form_control_element_for_testing_ = {}; |
| formless_elements_user_edited_.clear(); |
| formless_elements_were_autofilled_ = false; |
| provisionally_saved_form_.reset(); |
| } |
| |
| void AutofillAgent::UpdateLastInteractedForm( |
| const blink::WebFormElement& form) { |
| DCHECK(IsOwnedByFrame(form, render_frame())); |
| |
| last_interacted_form_ = form; |
| provisionally_saved_form_ = absl::make_optional<FormData>(); |
| if (!form_util::ExtractFormData(last_interacted_form_, |
| *field_data_manager_.get(), |
| &provisionally_saved_form_.value())) { |
| provisionally_saved_form_.reset(); |
| } |
| } |
| |
| void AutofillAgent::OnFormNoLongerSubmittable() { |
| submitted_forms_.clear(); |
| } |
| |
| mojom::AutofillDriver& AutofillAgent::GetAutofillDriver() { |
| if (IsPrerendering()) { |
| if (!deferring_autofill_driver_) { |
| deferring_autofill_driver_ = |
| std::make_unique<DeferringAutofillDriver>(this); |
| } |
| return *deferring_autofill_driver_; |
| } |
| |
| // Lazily bind this interface. |
| if (!autofill_driver_) { |
| render_frame()->GetRemoteAssociatedInterfaces()->GetInterface( |
| &autofill_driver_); |
| } |
| |
| return *autofill_driver_; |
| } |
| |
| mojom::PasswordManagerDriver& AutofillAgent::GetPasswordManagerDriver() { |
| DCHECK(password_autofill_agent_); |
| return password_autofill_agent_->GetPasswordManagerDriver(); |
| } |
| |
| } // namespace autofill |