| Index: components/autofill/ios/browser/resources/autofill_controller.js
|
| diff --git a/components/autofill/ios/browser/resources/autofill_controller.js b/components/autofill/ios/browser/resources/autofill_controller.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d4b976f5eaba980a8e2ccf7e08e1b09e0ad898ee
|
| --- /dev/null
|
| +++ b/components/autofill/ios/browser/resources/autofill_controller.js
|
| @@ -0,0 +1,1390 @@
|
| +// Copyright 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// Installs Autofill management functions on the |__gCrWeb| object.
|
| +//
|
| +// It scans the DOM, extracting and storing forms and returns a JSON string
|
| +// representing an array of objects, each of which represents an Autofill form
|
| +// with information about a form to be filled and/or submitted and it can be
|
| +// translated to struct FormData
|
| +// (chromium/src/components/autofill/common/form_data.h) for further processing.
|
| +
|
| +/**
|
| + * Namespace for this file. It depends on |__gCrWeb| having already been
|
| + * injected.
|
| + */
|
| +__gCrWeb['autofill'] = {};
|
| +
|
| +/**
|
| + * The maximum length allowed for form data.
|
| + *
|
| + * This variable is from
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.cc
|
| + */
|
| +__gCrWeb.autofill.MAX_DATA_LENGTH = 1024;
|
| +
|
| +/**
|
| + * The maximum number of form fields we are willing to parse, due to
|
| + * computational costs. Several examples of forms with lots of fields that are
|
| + * not relevant to Autofill: (1) the Netflix queue; (2) the Amazon wishlist;
|
| + * (3) router configuration pages; and (4) other configuration pages, e.g. for
|
| + * Google code project settings.
|
| + *
|
| + * This variable is from
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.MAX_PARSEABE_FIELDS = 100;
|
| +
|
| +/**
|
| + * A bit field mask for form or form element requirements for requirement
|
| + * none.
|
| + *
|
| + * This variable is from enum RequirementsMask in
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.REQUIREMENTS_MASK_NONE = 0;
|
| +
|
| +/**
|
| + * A bit field mask for form or form element requirements for requirement
|
| + * autocomplete != off.
|
| + *
|
| + * This variable is from enum RequirementsMask in
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE = 1;
|
| +
|
| +/**
|
| + * A bit field mask to extract data from WebFormControlElement for
|
| + * extracting none value.
|
| + *
|
| + * This variable is from enum ExtractMask in
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.EXTRACT_MASK_NONE = 0;
|
| +
|
| +/**
|
| + * A bit field mask to extract data from WebFormControlElement for
|
| + * extracting value from WebFormControlElement.
|
| + *
|
| + * This variable is from enum ExtractMask in
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.EXTRACT_MASK_VALUE = 1 << 0;
|
| +
|
| +/**
|
| + * A bit field mask to extract data from WebFormControlElement for
|
| + * extracting option text from WebFormSelectElement. Only valid when
|
| + * EXTRACT_MASK_VALUE is set. This is used for form submission where human
|
| + * readable value is captured.
|
| + *
|
| + * This variable is from enum ExtractMask in
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.EXTRACT_MASK_OPTION_TEXT = 1 << 1;
|
| +
|
| +/**
|
| + * A bit field mask to extract data from WebFormControlElement for
|
| + * extracting options from WebFormControlElement.
|
| + *
|
| + * This variable is from enum ExtractMask in
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.h
|
| + */
|
| +__gCrWeb.autofill.EXTRACT_MASK_OPTIONS = 1 << 2;
|
| +
|
| +/**
|
| + * The last element that was autofilled.
|
| + */
|
| +__gCrWeb.autofill.lastAutoFilledElement = null;
|
| +
|
| +/**
|
| + * Scans DOM and returns a JSON string representation of forms and form
|
| + * extraction results.
|
| + * @param {int} requiredFields The minimum number of fields forms must have to
|
| + * be extracted.
|
| + * @param {int} requirements The requirements mask for forms, e.g. autocomplete
|
| + * attribute state.
|
| + * @return {string} A JSON encoded object with object['forms'] containing the
|
| + * forms data and object['has_more_forms'] indicating if there are more
|
| + * forms to extract.
|
| + */
|
| +__gCrWeb.autofill['extractForms'] = function(requiredFields, requirements) {
|
| + var forms = [];
|
| + // Protect against custom implementation of Array.toJSON in host pages.
|
| + forms.toJSON = null;
|
| + // TODO(chenyu): check if any preparation is needed for information such as
|
| + // user_submitted or the one added in core.js is sufficient.
|
| + var hasMoreForms = __gCrWeb.autofill.extractFormsAndFormElements(
|
| + window,
|
| + requiredFields,
|
| + requirements,
|
| + forms);
|
| + var results = new __gCrWeb.common.JSONSafeObject;
|
| + results['has_more_forms'] = hasMoreForms;
|
| + results['forms'] = forms;
|
| + return __gCrWeb.stringify(results);
|
| +};
|
| +
|
| +/**
|
| + * Fills data into the active form field.
|
| + *
|
| + * @param {Object} data The data to fill in.
|
| + */
|
| +__gCrWeb.autofill['fillActiveFormField'] = function(data) {
|
| + var activeElement = document.activeElement;
|
| + if (data['name'] !== __gCrWeb['common'].nameForAutofill(activeElement)) {
|
| + return;
|
| + }
|
| + __gCrWeb.autofill.lastAutoFilledElement = activeElement;
|
| + __gCrWeb.autofill.fillFormField(data, activeElement);
|
| +};
|
| +
|
| +/**
|
| + * Fills a number of fields in the same named form.
|
| + *
|
| + * @param {Object} data The data to fill in.
|
| + */
|
| +__gCrWeb.autofill['fillForm'] = function(data) {
|
| + var form = __gCrWeb.common.getFormElementFromIdentifier(data.formName);
|
| + var controlElements = __gCrWeb.common.getFormControlElements(form);
|
| + for (var i = 0; i < controlElements.length; ++i) {
|
| + var element = controlElements[i];
|
| + if (!__gCrWeb.autofill.isAutofillableElement(element)) {
|
| + continue;
|
| + }
|
| + var value = data.fields[__gCrWeb['common'].nameForAutofill(element)];
|
| + if (value) {
|
| + element.value = value;
|
| + }
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Dispatch an autocomplete event to the named form.
|
| + *
|
| + * @param {String} name Identifier for form element (from getFormIdentifier).
|
| + */
|
| +__gCrWeb.autofill['dispatchAutocompleteEvent'] = function(name) {
|
| + var formElement = __gCrWeb.common.getFormElementFromIdentifier(name);
|
| + var event = new CustomEvent('autocomplete', {});
|
| + formElement.dispatchEvent(event);
|
| +};
|
| +
|
| +/**
|
| + * Dispatch an autocomplete error event to the named form.
|
| + *
|
| + * @param {String} name Identifier for form element (from getFormIdentifier).
|
| + * @param {String} reason Reason to supply in event.reason; one of 'cancel',
|
| + * 'invalid' or 'disabled'; see requestAutocomplete spec.
|
| + */
|
| +__gCrWeb.autofill['dispatchAutocompleteErrorEvent'] = function(name, reason) {
|
| + var formElement = __gCrWeb.common.getFormElementFromIdentifier(name);
|
| + var event = new CustomEvent('autocompleteerror', {});
|
| + event.reason = reason;
|
| + formElement.dispatchEvent(event);
|
| +};
|
| +
|
| +/**
|
| + * Dispatch an autocomplete error event to the named form.
|
| + *
|
| + * @param {String} name Identifier for form element (from getFormIdentifier).
|
| + * @param {String} reason Reason to supply in event.reason; one of 'cancel',
|
| + * 'invalid' or 'disabled'; see requestAutocomplete spec.
|
| + */
|
| +__gCrWeb.autofill['dispatchAutocompleteErrorEvent'] = function(name, reason) {
|
| + var formElement = __gCrWeb.common.getFormElementFromIdentifier(name);
|
| + var event = new CustomEvent('autocompleteerror', {});
|
| + event.reason = reason;
|
| + formElement.dispatchEvent(event);
|
| +};
|
| +
|
| +/**
|
| + * Scans the DOM in |frame| extracting and storing forms. Fills |forms| with
|
| + * extracted forms. Returns true if there are unextracted forms due to
|
| + * |minimumRequiredFields| limit, else false.
|
| + *
|
| + * This method is based on the logic in method
|
| + *
|
| + * bool FormCache::ExtractFormsAndFormElements(
|
| + * const WebFrame& frame,
|
| + * size_t minimum_required_fields,
|
| + * std::vector<FormData>* forms,
|
| + * std::vector<WebFormElement>* web_form_elements)
|
| + *
|
| + * in chromium/src/components/autofill/renderer/form_cache.cc.
|
| + *
|
| + * The difference is in this implementation, no form elements are returned in
|
| + * |web_form_elements|, and cache is not considered. Initial values of select
|
| + * and checkable elements are not recorded at the momemt.
|
| + *
|
| + * @param {Object} frame A window or a frame containing forms from which the
|
| + * data will be extracted.
|
| + * @param {int} minimumRequiredFields The minimum number of fields a form should
|
| + * contain for autofill.
|
| + * @param {int} requirements The requirements mask for forms, e.g. autocomplete
|
| + * attribute state.
|
| + * @param {Object} forms Forms that will be filled in data of forms in frame.
|
| + * @return {boolean} Whether there are unextracted forms due to
|
| + * |minimumRequiredFields| limit.
|
| + */
|
| +__gCrWeb.autofill.extractFormsAndFormElements = function(
|
| + frame, minimumRequiredFields, requirements, forms) {
|
| + if (!frame) {
|
| + return false;
|
| + }
|
| + var doc = frame.document;
|
| + if (!doc) {
|
| + return false;
|
| + }
|
| +
|
| + var webForms = doc.forms;
|
| +
|
| + var numFieldsSeen = 0;
|
| + var hasSkippedForms = false;
|
| + for (var formIndex = 0; formIndex < webForms.length; ++formIndex) {
|
| + var formElement = webForms[formIndex];
|
| + var controlElements = [];
|
| + __gCrWeb.autofill.extractAutofillableElements(
|
| + formElement, requirements, controlElements);
|
| + var numEditableElements = 0;
|
| + for (var elementIndex = 0; elementIndex < controlElements.length;
|
| + ++elementIndex) {
|
| + var element = controlElements[elementIndex];
|
| + if (!__gCrWeb.autofill.isCheckableElement(element)) {
|
| + ++numEditableElements;
|
| + }
|
| + }
|
| +
|
| + // To avoid overly expensive computation, we impose a minimum number of
|
| + // allowable fields. The corresponding maximum number of allowable
|
| + // fields is imposed by webFormElementToFormData().
|
| + if (numEditableElements < minimumRequiredFields) {
|
| + hasSkippedForms = true;
|
| + continue;
|
| + }
|
| +
|
| + var extractMask = __gCrWeb.autofill.EXTRACT_MASK_VALUE |
|
| + __gCrWeb.autofill.EXTRACT_MASK_OPTIONS;
|
| + var form = new __gCrWeb['common'].JSONSafeObject;
|
| + if (!__gCrWeb.autofill.webFormElementToFormData(
|
| + frame, formElement, null, requirements, extractMask, form)) {
|
| + continue;
|
| + }
|
| + numFieldsSeen += form['fields'].length;
|
| + if (numFieldsSeen > __gCrWeb.autofill.MAX_PARSEABE_FIELDS) {
|
| + break;
|
| + }
|
| +
|
| + if (form.fields.length >= minimumRequiredFields) {
|
| + forms.push(form);
|
| + } else {
|
| + hasSkippedForms = true;
|
| + }
|
| + }
|
| +
|
| + // Recursively invoke for all frames/iframes.
|
| + var frames = frame.frames;
|
| + for (var i = 0; i < frames.length; i++) {
|
| + var hasSkippedInframe = __gCrWeb.autofill.extractFormsAndFormElements(
|
| + frames[i], minimumRequiredFields, requirements, forms);
|
| + hasSkippedForms = hasSkippedForms || hasSkippedInframe;
|
| + }
|
| + return hasSkippedForms;
|
| +};
|
| +
|
| +/**
|
| + * Fills |form| with the form data object corresponding to the |formElement|.
|
| + * If |field| is non-NULL, also fills |field| with the FormField object
|
| + * corresponding to the |formControlElement|.
|
| + * |extract_mask| controls what data is extracted.
|
| + * Returns true if |form| is filled out; it's possible that the |formElement|
|
| + * won't meet the |requirements|. Also returns false if there are no fields or
|
| + * too many fields in the |form|.
|
| + *
|
| + * It is based on the logic in
|
| + * bool WebFormElementToFormData(
|
| + * const blink::WebFormElement& form_element,
|
| + * const blink::WebFormControlElement& form_control_element,
|
| + * RequirementsMask requirements,
|
| + * ExtractMask extract_mask,
|
| + * FormData* form,
|
| + * FormFieldData* field)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc
|
| + *
|
| + * @param {Element} frame The frame where the formElement is in.
|
| + * @param {Element} formElement The form element that will be processed.
|
| + * @param {Element} formControlElement A control element in formElment, the
|
| + * FormField of which will be returned in field.
|
| + * @param {int} requirements The requirement on formElement autocompletion.
|
| + * @param {int} extractMask Mask controls what data is extracted from
|
| + * formElement.
|
| + * @param {Object} form Form to fill in the FormData information of formElement.
|
| + * @param {Object} field Field to fill in the form field information of
|
| + * formControlElement.
|
| + * @return {boolean} Whether there are fields and not too many fields in the
|
| + * form.
|
| + */
|
| +__gCrWeb.autofill.webFormElementToFormData = function(
|
| + frame, formElement, formControlElement, requirements, extractMask, form,
|
| + field) {
|
| + if (!frame) {
|
| + return false;
|
| + }
|
| +
|
| + if ((requirements &
|
| + __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE) &&
|
| + !__gCrWeb['common'].autoComplete(formElement)) {
|
| + return false;
|
| + }
|
| +
|
| + form['name'] = __gCrWeb.common.getFormIdentifier(formElement);
|
| + var method = formElement.getAttribute('method');
|
| + if (method) {
|
| + form['method'] = method;
|
| + }
|
| + form['origin'] = __gCrWeb.common.removeQueryAndReferenceFromURL(
|
| + frame.location.href);
|
| + form['action'] = __gCrWeb.common.absoluteURL(
|
| + frame.document,
|
| + formElement.getAttribute('action'));
|
| + // TODO(chenyu): fill form['userSubmitted'] (Issue 231264).
|
| +
|
| + // Note different from form_autofill_utill.cc version of this method, which
|
| + // computes |form.action| using document.completeURL(form_element.action())
|
| + // and falls back to formElement.action() if the computed action is invalid,
|
| + // here the action returned by |__gCrWeb.common.absoluteURL| is always
|
| + // valid, which is computed by creating a <a> element, and we don't check if
|
| + // the action is valid.
|
| +
|
| + // A map from a FormFieldData's name to the FormFieldData itself.
|
| + var nameMap = {};
|
| +
|
| + // The extracted FormFields.
|
| + var formFields = [];
|
| +
|
| + var controlElements = __gCrWeb['common'].getFormControlElements(formElement);
|
| +
|
| + // A vector of bools that indicate whether each element in |controlElements|
|
| + // meets the requirements and thus will be in the resulting |form|.
|
| + var fieldsExtracted = [];
|
| +
|
| + for (var i = 0; i < controlElements.length; ++i) {
|
| + fieldsExtracted[i] = false;
|
| +
|
| + var controlElement = controlElements[i];
|
| + if (!__gCrWeb.autofill.isAutofillableElement(controlElement)) {
|
| + continue;
|
| + }
|
| +
|
| + if (requirements &
|
| + __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE &&
|
| + __gCrWeb.autofill.isAutofillableInputElement(controlElement) &&
|
| + !__gCrWeb.autofill.satisfiesRequireAutocomplete(
|
| + controlElement, false)) {
|
| + continue;
|
| + }
|
| +
|
| + // Create a new FormFieldData, fill it out and map it to the field's name.
|
| + var formField = new __gCrWeb['common'].JSONSafeObject;
|
| + __gCrWeb.autofill.webFormControlElementToFormField(
|
| + controlElement, extractMask, formField);
|
| + formFields.push(formField);
|
| + nameMap[formField['name']] = formField;
|
| + fieldsExtracted[i] = true;
|
| +
|
| + // To avoid overly expensive computation, we impose a maximum number of
|
| + // allowable fields.
|
| + if (formFields.length > __gCrWeb.autofill.MAX_PARSEABE_FIELDS) {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + // If we failed to extract any fields, give up.
|
| + if (formFields.length === 0) {
|
| + return false;
|
| + }
|
| +
|
| + // Loop through the label elements inside the form element. For each label
|
| + // element, get the corresponding form control element, use the form control
|
| + // element's name as a key into the <name, FormFieldData> nameMap to find the
|
| + // previously created FormFieldData and set the FormFieldData's label.
|
| + var labels = formElement.getElementsByTagName('label');
|
| + for (var index = 0; index < labels.length; ++index) {
|
| + var label = labels[index];
|
| + if (!label.control && !label.htmlFor) {
|
| + continue;
|
| + }
|
| + var fieldElement = label.control;
|
| + var elementName;
|
| + if (!fieldElement) {
|
| + // Sometimes site authors will incorrectly specify the corresponding
|
| + // field element's name rather than its id, so we compensate here.
|
| + elementName = label.htmlFor;
|
| + } else if (fieldElement.form != formElement ||
|
| + fieldElement.type === 'hidden') {
|
| + continue;
|
| + } else {
|
| + elementName = __gCrWeb['common'].nameForAutofill(fieldElement);
|
| + }
|
| +
|
| + var fieldElementData = nameMap[elementName];
|
| + if (fieldElementData) {
|
| + if (!fieldElementData['label']) {
|
| + fieldElementData['label'] = '';
|
| + }
|
| + var labelText = __gCrWeb.autofill.findChildText(label);
|
| + // Concatenate labels because some sites might have multiple label
|
| + // candidates.
|
| + if (fieldElementData['label'].length > 0 &&
|
| + labelText.length > 0) {
|
| + fieldElementData['label'] += ' ';
|
| + }
|
| + fieldElementData['label'] += labelText;
|
| + }
|
| + }
|
| +
|
| + // Loop through the form control elements, extracting the label text from
|
| + // the DOM. We use the |fieldsExtracted| vector to make sure we assign the
|
| + // extracted label to the correct field, as it's possible |form_fields| will
|
| + // not contain all of the elements in |control_elements|.
|
| + for (var i = 0, fieldIdx = 0;
|
| + i < controlElements.length && fieldIdx < formFields.length; ++i) {
|
| + // This field didn't meet the requirements, so don't try to find a label
|
| + // for it.
|
| + if (!fieldsExtracted[i])
|
| + continue;
|
| +
|
| + var controlElement = controlElements[i];
|
| + var fieldLabel = formFields[fieldIdx]['label'];
|
| + if (!fieldLabel || fieldLabel.length === 0) {
|
| + formFields[fieldIdx]['label'] =
|
| + __gCrWeb.autofill.inferLabelForElement(controlElement);
|
| + }
|
| + if (controlElement === formControlElement)
|
| + field = formField;
|
| + ++fieldIdx;
|
| + }
|
| +
|
| + form['fields'] = formFields;
|
| + // Protect against custom implementation of Array.toJSON in host pages.
|
| + form['fields'].toJSON = null;
|
| + return true;
|
| +};
|
| +
|
| +/**
|
| + * Returns is the tag of an |element| is tag.
|
| + *
|
| + * It is based on the logic in
|
| + * bool HasTagName(const WebNode& node, const blink::WebString& tag)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} node Node to examine.
|
| + * @param {string} tag Tag name.
|
| + * @return {boolean} Whether the tag of node is tag.
|
| + */
|
| +__gCrWeb.autofill.hasTagName = function(node, tag) {
|
| + return node.nodeType === document.ELEMENT_NODE &&
|
| + node.tagName === tag.toUpperCase();
|
| +};
|
| +
|
| +/**
|
| + * Checks if an element is autofillable.
|
| + *
|
| + * It is based on the logic in
|
| + * bool IsAutofillableElement(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {boolean} Whether element is one of the element types that can be
|
| + * autofilled.
|
| + */
|
| +__gCrWeb.autofill.isAutofillableElement = function(element) {
|
| + return __gCrWeb.autofill.isAutofillableInputElement(element) ||
|
| + __gCrWeb.autofill.isSelectElement(element) ||
|
| + __gCrWeb.autofill.isTextAreaElement(element);
|
| +};
|
| +
|
| +/**
|
| + * Check whether the given field satisfies the
|
| + * __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE requirement. When
|
| + * Autocheckout is enabled, all fields are considered to satisfy this
|
| + * requirement.
|
| + *
|
| + * It is based on the logic in
|
| + * bool SatisfiesRequireAutocomplete(const WebInputElement& input_element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element The element to be examined.
|
| + * @param {boolean} isExperimentalFormFillingEnabled Boolean from
|
| + * switches::kEnableExperimentalFormFilling.
|
| + * @return {boolean} Whether the inputElement satisfies the requirement.
|
| + */
|
| +__gCrWeb.autofill.satisfiesRequireAutocomplete = function(
|
| + element, isExperimentalFormFillingEnabled) {
|
| + return __gCrWeb.common.autoComplete(element) ||
|
| + isExperimentalFormFillingEnabled;
|
| +};
|
| +
|
| +/**
|
| + * Trims whitespace from the start of the input string.
|
| + * Simplified version of string_util::TrimWhitespace.
|
| + * @param {string} input String to trim.
|
| + * @return {string} The |input| string without leading whitespace.
|
| + */
|
| +__gCrWeb.autofill.trimWhitespaceLeading = function(input) {
|
| + return input.replace(/^\s+/gm, '');
|
| +};
|
| +
|
| +/**
|
| + * Trims whitespace from the end of the input string.
|
| + * Simplified version of string_util::TrimWhitespace.
|
| + * @param {string} input String to trim.
|
| + * @return {string} The |input| string without trailing whitespace.
|
| + */
|
| +__gCrWeb.autofill.trimWhitespaceTrailing = function(input) {
|
| + return input.replace(/\s+$/gm, '');
|
| +};
|
| +
|
| +/**
|
| + * Appends |suffix| to |prefix| so that any intermediary whitespace is collapsed
|
| + * to a single space. If |force_whitespace| is true, then the resulting string
|
| + * is guaranteed to have a space between |prefix| and |suffix|. Otherwise, the
|
| + * result includes a space only if |prefix| has trailing whitespace or |suffix|
|
| + * has leading whitespace.
|
| + *
|
| + * A few examples:
|
| + * CombineAndCollapseWhitespace('foo', 'bar', false) -> 'foobar'
|
| + * CombineAndCollapseWhitespace('foo', 'bar', true) -> 'foo bar'
|
| + * CombineAndCollapseWhitespace('foo ', 'bar', false) -> 'foo bar'
|
| + * CombineAndCollapseWhitespace('foo', ' bar', false) -> 'foo bar'
|
| + * CombineAndCollapseWhitespace('foo', ' bar', true) -> 'foo bar'
|
| + * CombineAndCollapseWhitespace('foo ', ' bar', false) -> 'foo bar'
|
| + * CombineAndCollapseWhitespace(' foo', 'bar ', false) -> ' foobar '
|
| + * CombineAndCollapseWhitespace(' foo', 'bar ', true) -> ' foo bar '
|
| + *
|
| + * It is based on the logic in
|
| + * const string16 CombineAndCollapseWhitespace(const string16& prefix,
|
| + * const string16& suffix,
|
| + * bool force_whitespace)
|
| + * @param {string} prefix The prefix string in the string combination.
|
| + * @param {string} suffix The suffix string in the string combination.
|
| + * @param {boolean} forceWhitespace A boolean indicating if whitespace should
|
| + * be added as separator in the combination.
|
| + * @return {string} The combined string.
|
| + */
|
| +__gCrWeb.autofill.combineAndCollapseWhitespace = function(
|
| + prefix, suffix, forceWhitespace) {
|
| + var prefixTrimmed = __gCrWeb.autofill.trimWhitespaceTrailing(prefix);
|
| + var prefixTrailingWhitespace = prefixTrimmed != prefix;
|
| + var suffixTrimmed = __gCrWeb.autofill.trimWhitespaceLeading(suffix);
|
| + var suffixLeadingWhitespace = suffixTrimmed != suffix;
|
| + if (prefixTrailingWhitespace || suffixLeadingWhitespace || forceWhitespace) {
|
| + return prefixTrimmed + ' ' + suffixTrimmed;
|
| + } else {
|
| + return prefixTrimmed + suffixTrimmed;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * This is a helper function for the findChildText() function (see below).
|
| + * Search depth is limited with the |depth| parameter.
|
| + * Based on form_autofill_util::findChildTextInner().
|
| + * @param {Element} node The node to fetch the text content from.
|
| + * @param {int} depth The maximum depth to descend on the DOM.
|
| + * @return {string} The discovered and adapted string.
|
| + */
|
| +__gCrWeb.autofill.findChildTextInner = function(node, depth) {
|
| + if (depth <= 0 || !node) {
|
| + return '';
|
| + }
|
| +
|
| + // Skip over comments.
|
| + if (node.nodeType === document.COMMENT_NODE) {
|
| + return __gCrWeb.autofill.findChildTextInner(node.nextSibling, depth - 1);
|
| + }
|
| +
|
| + if (node.nodeType !== document.ELEMENT_NODE &&
|
| + node.nodeType !== document.TEXT_NODE) {
|
| + return '';
|
| + }
|
| +
|
| + // Ignore elements known not to contain inferable labels.
|
| + if (node.nodeType === document.ELEMENT_NODE) {
|
| + if (node.tagName === 'OPTION' ||
|
| + node.tagName === 'SCRIPT' ||
|
| + node.tagName === 'NOSCRIPT' ||
|
| + (__gCrWeb.common.isFormControlElement(node) &&
|
| + __gCrWeb.autofill.isAutofillableElement(node))) {
|
| + return '';
|
| + }
|
| + }
|
| +
|
| + // Extract the text exactly at this node.
|
| + var nodeText = __gCrWeb.autofill.nodeValue(node);
|
| + if (node.nodeType === document.TEXT_NODE && !nodeText) {
|
| + // In the C++ version, this text node would have been stripped completely.
|
| + // Just pass the buck.
|
| + return __gCrWeb.autofill.findChildTextInner(node.nextSibling, depth);
|
| + }
|
| +
|
| + // Recursively compute the children's text.
|
| + // Preserve inter-element whitespace separation.
|
| + var childText =
|
| + __gCrWeb.autofill.findChildTextInner(node.firstChild, depth - 1);
|
| + var addSpace = node.nodeType === document.TEXT_NODE && !nodeText;
|
| + // Emulate apparently incorrect Chromium behavior tracked in crbug 239819.
|
| + addSpace = false;
|
| + nodeText = __gCrWeb.autofill.combineAndCollapseWhitespace(nodeText,
|
| + childText, addSpace);
|
| +
|
| + // Recursively compute the siblings' text.
|
| + // Again, preserve inter-element whitespace separation.
|
| + var siblingText =
|
| + __gCrWeb.autofill.findChildTextInner(node.nextSibling, depth - 1);
|
| + addSpace = node.nodeType === document.TEXT_NODE && !nodeText;
|
| + // Emulate apparently incorrect Chromium behavior tracked in crbug 239819.
|
| + addSpace = false;
|
| + nodeText = __gCrWeb.autofill.combineAndCollapseWhitespace(nodeText,
|
| + siblingText, addSpace);
|
| +
|
| + return nodeText;
|
| +};
|
| +
|
| +/**
|
| + * Returns the aggregated values of the descendants of |element| that are
|
| + * non-empty text nodes.
|
| + *
|
| + * It is based on the logic in
|
| + * string16 FindChildText(const WebNode& node)
|
| + * chromium/src/components/autofill/renderer/form_autofill_util.cc, which is a
|
| + * faster alternative to |innerText()| for performance critical operations.
|
| + *
|
| + * @param {Element} node A node of which the child text will be return.
|
| + * @return {string} The child text.
|
| + */
|
| +__gCrWeb.autofill.findChildText = function(node) {
|
| + if (node.nodeType === document.TEXT_NODE)
|
| + return __gCrWeb.autofill.nodeValue(node);
|
| + var child = node.firstChild;
|
| +
|
| + var kChildSearchDepth = 10;
|
| + var nodeText = __gCrWeb.autofill.findChildTextInner(child, kChildSearchDepth);
|
| + nodeText = nodeText.trim();
|
| + return nodeText;
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * a previous sibling of |element|,
|
| + * e.g. Some Text <input ...>
|
| + * or Some <span>Text</span> <input ...>
|
| + * or <p>Some Text</p><input ...>
|
| + * or <label>Some Text</label> <input ...>
|
| + * or Some Text <img><input ...>
|
| + * or <b>Some Text</b><br/> <input ...>.
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromPrevious(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromPrevious = function(element) {
|
| + var inferredLabel = '';
|
| + var previous = element;
|
| + if (!previous) {
|
| + return '';
|
| + }
|
| +
|
| + while (true) {
|
| + previous = previous.previousSibling;
|
| + if (!previous) {
|
| + break;
|
| + }
|
| +
|
| + // Skip over comments.
|
| + var nodeType = previous.nodeType;
|
| + if (nodeType === document.COMMENT_NODE_NODE) {
|
| + continue;
|
| + }
|
| +
|
| + // Otherwise, only consider normal HTML elements and their contents.
|
| + if (nodeType != document.TEXT_NODE &&
|
| + nodeType != document.ELEMENT_NODE) {
|
| + break;
|
| + }
|
| +
|
| + // A label might be split across multiple "lightweight" nodes.
|
| + // Coalesce any text contained in multiple consecutive
|
| + // (a) plain text nodes or
|
| + // (b) inline HTML elements that are essentially equivalent to text nodes.
|
| + if (nodeType === document.TEXT_NODE ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'b') ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'strong') ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'span') ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'font')) {
|
| + var value = __gCrWeb.autofill.findChildText(previous);
|
| + // A text node's value will be empty if it is for a line break.
|
| + var addSpace = nodeType === document.TEXT_NODE &&
|
| + value.length === 0;
|
| + inferredLabel =
|
| + __gCrWeb.autofill.combineAndCollapseWhitespace(
|
| + value, inferredLabel, addSpace);
|
| + continue;
|
| + }
|
| +
|
| + // If we have identified a partial label and have reached a non-lightweight
|
| + // element, consider the label to be complete.
|
| + var trimmedLabel = inferredLabel.trim();
|
| + if (trimmedLabel.length > 0) {
|
| + break;
|
| + }
|
| +
|
| + // <img> and <br> tags often appear between the input element and its
|
| + // label text, so skip over them.
|
| + if (__gCrWeb.autofill.hasTagName(previous, 'img') ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'br')) {
|
| + continue;
|
| + }
|
| +
|
| + // We only expect <p> and <label> tags to contain the full label text.
|
| + if (__gCrWeb.autofill.hasTagName(previous, 'p') ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'label')) {
|
| + inferredLabel = __gCrWeb.autofill.findChildText(previous);
|
| + }
|
| + break;
|
| + }
|
| + return inferredLabel.trim();
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * the placeholder attribute.
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromPlaceholder(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromPlaceholder = function(element) {
|
| + if (!element || !element.placeholder) {
|
| + return '';
|
| + }
|
| +
|
| + return element.placeholder;
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * enclosing list item, e.g.
|
| + * <li>Some Text<input ...><input ...><input ...></li>
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromListItem(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromListItem = function(element) {
|
| + if (!element) {
|
| + return '';
|
| + }
|
| +
|
| + var parent = element.parentNode;
|
| + while (parent &&
|
| + parent.nodeType === document.ELEMENT_NODE &&
|
| + !__gCrWeb.autofill.hasTagName(parent, 'li')) {
|
| + parent = parent.parentNode;
|
| + }
|
| +
|
| + if (parent && __gCrWeb.autofill.hasTagName(parent, 'li'))
|
| + return __gCrWeb.autofill.findChildText(parent);
|
| +
|
| + return '';
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * surrounding table structure,
|
| + * e.g. <tr><td>Some Text</td><td><input ...></td></tr>
|
| + * or <tr><th>Some Text</th><td><input ...></td></tr>
|
| + * or <tr><td><b>Some Text</b></td><td><b><input ...></b></td></tr>
|
| + * or <tr><th><b>Some Text</b></th><td><b><input ...></b></td></tr>
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromTableColumn(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromTableColumn = function(element) {
|
| + if (!element) {
|
| + return '';
|
| + }
|
| +
|
| + var parent = element.parentNode;
|
| + while (parent &&
|
| + parent.nodeType === document.ELEMENT_NODE &&
|
| + !__gCrWeb.autofill.hasTagName(parent, 'td')) {
|
| + parent = parent.parentNode;
|
| + }
|
| +
|
| + if (!parent) {
|
| + return '';
|
| + }
|
| +
|
| + // Check all previous siblings, skipping non-element nodes, until we find a
|
| + // non-empty text block.
|
| + var inferredLabel = '';
|
| + var previous = parent.previousSibling;
|
| + while (inferredLabel.length === 0 && previous) {
|
| + if (__gCrWeb.autofill.hasTagName(previous, 'td') ||
|
| + __gCrWeb.autofill.hasTagName(previous, 'th')) {
|
| + inferredLabel = __gCrWeb.autofill.findChildText(previous);
|
| + }
|
| + previous = previous.previousSibling;
|
| + }
|
| +
|
| + return inferredLabel;
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * surrounding table structure,
|
| + * e.g. <tr><td>Some Text</td></tr><tr><td><input ...></td></tr>
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromTableRow(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromTableRow = function(element) {
|
| + if (!element) {
|
| + return '';
|
| + }
|
| +
|
| + var parent = element.parentNode;
|
| + while (parent &&
|
| + parent.nodeType === document.ELEMENT_NODE &&
|
| + !__gCrWeb.autofill.hasTagName(parent, 'tr')) {
|
| + parent = parent.parentNode;
|
| + }
|
| +
|
| + if (!parent) {
|
| + return '';
|
| + }
|
| +
|
| + var inferredLabel = '';
|
| + // Check all previous siblings, skipping non-element nodes, until we find a
|
| + // non-empty text block.
|
| + var previous = parent.previousSibling;
|
| + while (inferredLabel.length === 0 && previous) {
|
| + if (__gCrWeb.autofill.hasTagName(previous, 'tr')) {
|
| + inferredLabel = __gCrWeb.autofill.findChildText(previous);
|
| + }
|
| + previous = previous.previousSibling;
|
| + }
|
| + return inferredLabel;
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * a surrounding div table,
|
| + * e.g. <div>Some Text<span><input ...></span></div>
|
| + * e.g. <div>Some Text</div><div><input ...></div>
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromDivTable(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromDivTable = function(element) {
|
| + if (!element) {
|
| + return '';
|
| + }
|
| +
|
| + var node = element.parentNode;
|
| + var lookingForParent = true;
|
| +
|
| + // Search the sibling and parent <div>s until we find a candidate label.
|
| + var inferredLabel = '';
|
| + while (inferredLabel.length === 0 && node) {
|
| + if (__gCrWeb.autofill.hasTagName(node, 'div')) {
|
| + lookingForParent = false;
|
| + inferredLabel = __gCrWeb.autofill.findChildText(node);
|
| + } else if (lookingForParent &&
|
| + (__gCrWeb.autofill.hasTagName(node, 'table') ||
|
| + __gCrWeb.autofill.hasTagName(node, 'fieldset'))) {
|
| + // If the element is in a table or fieldset, its label most likely is too.
|
| + break;
|
| + }
|
| +
|
| + if (!node.previousSibling) {
|
| + // If there are no more siblings, continue walking up the tree.
|
| + lookingForParent = true;
|
| + }
|
| +
|
| + if (lookingForParent) {
|
| + node = node.parentNode;
|
| + } else {
|
| + node = node.previousSibling;
|
| + }
|
| + }
|
| +
|
| + return inferredLabel;
|
| +};
|
| +
|
| +/**
|
| + * Helper for |InferLabelForElement()| that infers a label, if possible, from
|
| + * a surrounding definition list,
|
| + * e.g. <dl><dt>Some Text</dt><dd><input ...></dd></dl>
|
| + * e.g. <dl><dt><b>Some Text</b></dt><dd><b><input ...></b></dd></dl>
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelFromDefinitionList(
|
| + * const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelFromDefinitionList = function(element) {
|
| + if (!element) {
|
| + return '';
|
| + }
|
| +
|
| + var parent = element.parentNode;
|
| + while (parent &&
|
| + parent.nodeType === document.ELEMENT_NODE &&
|
| + !__gCrWeb.autofill.hasTagName(parent, 'dd')) {
|
| + parent = parent.parentNode;
|
| + }
|
| +
|
| + if (!parent || !__gCrWeb.autofill.hasTagName(parent, 'dd')) {
|
| + return '';
|
| + }
|
| +
|
| + // Skip by any intervening text nodes.
|
| + var previous = parent.previousSibling;
|
| + while (previous &&
|
| + previous.nodeType === document.TEXT_NODE) {
|
| + previous = previous.previousSibling;
|
| + }
|
| +
|
| + if (!previous || !__gCrWeb.autofill.hasTagName(previous, 'dt'))
|
| + return '';
|
| +
|
| + return __gCrWeb.autofill.findChildText(previous);
|
| +};
|
| +
|
| +/**
|
| + * Infers corresponding label for |element| from surrounding context in the DOM,
|
| + * e.g. the contents of the preceding <p> tag or text element.
|
| + *
|
| + * It is based on the logic in
|
| + * string16 InferLabelForElement(const WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The label of element.
|
| + */
|
| +__gCrWeb.autofill.inferLabelForElement = function(element) {
|
| + var inferredLabel = __gCrWeb.autofill.inferLabelFromPrevious(element);
|
| + if (inferredLabel.length > 0) {
|
| + return inferredLabel;
|
| + }
|
| +
|
| + // If we didn't find a label, check for the placeholder case.
|
| + inferredLabel = __gCrWeb.autofill.inferLabelFromPlaceholder(element);
|
| + if (inferredLabel.length > 0) {
|
| + return inferredLabel;
|
| + }
|
| +
|
| + // If we didn't find a label, check for list item case.
|
| + inferredLabel = __gCrWeb.autofill.inferLabelFromListItem(element);
|
| + if (inferredLabel.length > 0) {
|
| + return inferredLabel;
|
| + }
|
| +
|
| + // If we didn't find a label, check for table cell case.
|
| + inferredLabel = __gCrWeb.autofill.inferLabelFromTableColumn(element);
|
| + if (inferredLabel.length > 0) {
|
| + return inferredLabel;
|
| + }
|
| +
|
| + // If we didn't find a label, check for table row case.
|
| + inferredLabel = __gCrWeb.autofill.inferLabelFromTableRow(element);
|
| + if (inferredLabel.length > 0) {
|
| + return inferredLabel;
|
| + }
|
| +
|
| + // If we didn't find a label, check for definition list case.
|
| + inferredLabel = __gCrWeb.autofill.inferLabelFromDefinitionList(element);
|
| + if (inferredLabel.length > 0) {
|
| + return inferredLabel;
|
| + }
|
| +
|
| + // If we didn't find a label, check for div table case.
|
| + return __gCrWeb.autofill.inferLabelFromDivTable(element);
|
| +};
|
| +
|
| +/**
|
| + * Fills |field| data with the values of the <option> elements present in
|
| + * |selectElement|.
|
| + *
|
| + * It is based on the logic in
|
| + * void GetOptionStringsFromElement(const WebSelectElement& select_element,
|
| + * std::vector<string16>* option_values,
|
| + * std::vector<string16>* option_contents)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * @param {Element} selectElement A select element from which option data are
|
| + * extracted.
|
| + * @param {Object} field A field that will contain the extracted option
|
| + * information.
|
| + */
|
| +__gCrWeb.autofill.getOptionStringsFromElement = function(
|
| + selectElement, field) {
|
| + field['option_values'] = [];
|
| + // Protect against custom implementation of Array.toJSON in host pages.
|
| + field['option_values'].toJSON = null;
|
| + field['option_contents'] = [];
|
| + field['option_contents'].toJSON = null;
|
| + var options = selectElement.options;
|
| + for (var i = 0; i < options.length; ++i) {
|
| + var option = options[i];
|
| + field['option_values'].push(option['value']);
|
| + field['option_contents'].push(option['text']);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Sets the |field|'s value to the value in |data|.
|
| + * Also sets the "autofilled" attribute.
|
| + *
|
| + * It is based on the logic in
|
| + * void FillFormField(const FormFieldData& data,
|
| + * bool is_initiating_node,
|
| + * blink::WebFormControlElement* field)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.cc.
|
| + *
|
| + * Different from FillFormField(), is_initiating_node is not considered in
|
| + * this implementation.
|
| + *
|
| + * @param {Object} data Data that will be filled into field.
|
| + * @param {Element} field The element to which data will be filled.
|
| + */
|
| +__gCrWeb.autofill.fillFormField = function(data, field) {
|
| + // Nothing to fill.
|
| + if (!data['value'] || data['value'].length === 0) {
|
| + return;
|
| + }
|
| +
|
| + if (__gCrWeb.autofill.isTextInput(field) ||
|
| + __gCrWeb.autofill.isTextAreaElement(field)) {
|
| + var sanitizedValue = data['value'];
|
| +
|
| + if (__gCrWeb.autofill.isTextInput(field)) {
|
| + // If the 'max_length' attribute contains a negative value, the default
|
| + // maxlength value is used.
|
| + var maxLength = data['max_length'];
|
| + if (maxLength < 0) {
|
| + maxLength = __gCrWeb.autofill.MAX_DATA_LENGTH;
|
| + }
|
| + sanitizedValue = data['value'].substr(0, maxLength);
|
| + }
|
| +
|
| + __gCrWeb.common.setInputElementValue(sanitizedValue, field, true);
|
| + field.isAutofilled = true;
|
| + } else if (__gCrWeb.autofill.isSelectElement(field)) {
|
| + if (field.value !== data['value']) {
|
| + field.value = data['value'];
|
| + __gCrWeb.common.createAndDispatchHTMLEvent(field, 'change', true, false);
|
| + }
|
| + } else {
|
| + if (__gCrWeb.autofill.isCheckableElement(field)) {
|
| + __gCrWeb.common.setInputElementChecked(data['is_checked'], field, true);
|
| + }
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Returns true if |element| is a text input element.
|
| + *
|
| + * It is based on the logic in
|
| + * bool IsTextInput(const blink::WebInputElement* element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {boolean} Whether element is a text input field.
|
| + */
|
| +__gCrWeb.autofill.isTextInput = function(element) {
|
| + if (!element) {
|
| + return false;
|
| + }
|
| + return __gCrWeb.common.isTextField(element);
|
| +};
|
| +
|
| +/**
|
| + * Returns true if |element| is a 'select' element.
|
| + *
|
| + * It is based on the logic in
|
| + * bool IsSelectElement(const blink::WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {boolean} Whether element is a 'select' element.
|
| + */
|
| +__gCrWeb.autofill.isSelectElement = function(element) {
|
| + if (!element) {
|
| + return false;
|
| + }
|
| + return element.type === 'select-one';
|
| +};
|
| +
|
| +/**
|
| + * Returns true if |element| is a 'textarea' element.
|
| + *
|
| + * It is based on the logic in
|
| + * bool IsTextAreaElement(const blink::WebFormControlElement& element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {boolean} Whether element is a 'textarea' element.
|
| + */
|
| +__gCrWeb.autofill.isTextAreaElement = function(element) {
|
| + if (!element) {
|
| + return false;
|
| + }
|
| + return element.type === 'textarea';
|
| +};
|
| +
|
| +/**
|
| + * Returns true if |element| is a checkbox or a radio button element.
|
| + *
|
| + * It is based on the logic in
|
| + * bool IsCheckableElement(const blink::WebInputElement* element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {boolean} Whether element is a checkbox or a radio button.
|
| + */
|
| +__gCrWeb.autofill.isCheckableElement = function(element) {
|
| + if (!element) {
|
| + return false;
|
| + }
|
| + return element.type === 'checkbox' || element.type === 'radio';
|
| +};
|
| +
|
| +/**
|
| + * Returns true if |element| is one of the input element types that can be
|
| + * autofilled. {Text, Radiobutton, Checkbox}.
|
| + *
|
| + * It is based on the logic in
|
| + * bool IsAutofillableInputElement(const blink::WebInputElement* element)
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {boolean} Whether element is one of the input element types that
|
| + * can be autofilled.
|
| + */
|
| +__gCrWeb.autofill.isAutofillableInputElement = function(element) {
|
| + return __gCrWeb.autofill.isTextInput(element) ||
|
| + __gCrWeb.autofill.isCheckableElement(element);
|
| +};
|
| +
|
| +/**
|
| + * Returns the nodeValue in a way similar to the C++ version of node.nodeValue,
|
| + * used in src/components/autofill/renderer/form_autofill_util.h. Newlines and
|
| + * tabs are stripped.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The text contained in |element|.
|
| + */
|
| +__gCrWeb.autofill.nodeValue = function(element) {
|
| + return (element.nodeValue || '').replace(/[\n\t]/gm, '');
|
| +};
|
| +
|
| +/**
|
| + * Returns the value in a way similar to the C++ version of node.value,
|
| + * used in src/components/autofill/renderer/form_autofill_util.h. Newlines and
|
| + * tabs are stripped.
|
| + *
|
| + * @param {Element} element An element to examine.
|
| + * @return {string} The value for |element|.
|
| + */
|
| +__gCrWeb.autofill.value = function(element) {
|
| + return (element.value || '').replace(/[\n\t]/gm, '');
|
| +};
|
| +
|
| +/**
|
| + * Returns the auto-fillable form control elements in |formElement|.
|
| + *
|
| + * It is based on the logic in
|
| + * void ExtractAutofillableElements(
|
| + * const blink::WebFormElement& form_element,
|
| + * RequirementsMask requirements,
|
| + * std::vector<blink::WebFormControlElement>* autofillable_elements);
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} formElement A form element to be processed.
|
| + * @param {int} requirementsMask A mask on the requirement.
|
| + * @param {Array.<Element>} autofillableElements The array of autofillable
|
| + * elements.
|
| + */
|
| +__gCrWeb.autofill.extractAutofillableElements = function(
|
| + formElement, requirementsMask, autofillableElements) {
|
| + var controlElements = __gCrWeb.common.getFormControlElements(formElement);
|
| +
|
| + for (var i = 0; i < controlElements.length; ++i) {
|
| + var element = controlElements[i];
|
| + if (!__gCrWeb.autofill.isAutofillableElement(element)) {
|
| + continue;
|
| + }
|
| + if (requirementsMask &
|
| + __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE) {
|
| + // Different from method void ExtractAutofillableElements() in
|
| + // chromium/src/components/autofill/renderer/form_autofill_util.h, where
|
| + // satisfiesRequireAutocomplete() check is only applied on input controls,
|
| + // here satisfiesRequireAutocomplete() check is also applied on select
|
| + // control element. This is based on the TODO in that file saying "WebKit
|
| + // currently doesn't handle the autocomplete attribute for select control
|
| + // elements, but it probably should."
|
| + if (!__gCrWeb.autofill.satisfiesRequireAutocomplete(element, false)) {
|
| + continue;
|
| + }
|
| + }
|
| + autofillableElements.push(element);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Fills out a FormField object from a given form control element.
|
| + *
|
| + * It is based on the logic in
|
| + * void WebFormControlElementToFormField(
|
| + * const blink::WebFormControlElement& element,
|
| + * ExtractMask extract_mask,
|
| + * FormFieldData* field);
|
| + * in chromium/src/components/autofill/renderer/form_autofill_util.h.
|
| + *
|
| + * @param {Element} element The element to be processed.
|
| + * @param {int} extractMask A bit field mask to extract data from |element|.
|
| + * See the document on variable __gCrWeb.autofill.EXTRACT_MASK_NONE,
|
| + * __gCrWeb.autofill.EXTRACT_MASK_VALUE,
|
| + * __gCrWeb.autofill.EXTRACT_MASK_OPTION_TEXT and
|
| + * __gCrWeb.autofill.EXTRACT_MASK_OPTIONS.
|
| + * @param {Object} field Field to fill in the element information.
|
| + */
|
| +__gCrWeb.autofill.webFormControlElementToFormField = function(
|
| + element, extractMask, field) {
|
| + if (!field || !element) {
|
| + return;
|
| + }
|
| + // The label is not officially part of a form control element; however, the
|
| + // labels for all form control elements are scraped from the DOM and set in
|
| + // form data.
|
| + field['name'] = __gCrWeb['common'].nameForAutofill(element);
|
| + field['form_control_type'] = element.type;
|
| + var attribute = element.getAttribute('autocomplete');
|
| + if (attribute) {
|
| + field['autocomplete_attribute'] = attribute;
|
| + }
|
| + if (field['autocomplete_attribute'] != null &&
|
| + field['autocomplete_attribute'].length >
|
| + __gCrWeb.autofill.MAX_DATA_LENGTH) {
|
| + // Discard overly long attribute values to avoid DOS-ing the browser
|
| + // process. However, send over a default string to indicate that the
|
| + // attribute was present.
|
| + field['autocomplete_attribute'] = 'x-max-data-length-exceeded';
|
| + }
|
| +
|
| + if (!__gCrWeb.autofill.isAutofillableElement(element)) {
|
| + return;
|
| + }
|
| +
|
| + if (__gCrWeb.autofill.isAutofillableInputElement(element) ||
|
| + __gCrWeb.autofill.isTextAreaElement(element)) {
|
| + field['is_autofilled'] = element.isAutofilled;
|
| + field['should_autocomplete'] = __gCrWeb.common.autoComplete(element);
|
| + // TODO(chenyu): compute this item properly.
|
| + // field['is_focusable'] = element.isFocusable;
|
| + }
|
| +
|
| + if (__gCrWeb.autofill.isAutofillableInputElement(element)) {
|
| + if (__gCrWeb.autofill.isTextInput(element)) {
|
| + field['max_length'] = element.maxLength;
|
| + }
|
| + field['is_checkable'] = __gCrWeb.autofill.isCheckableElement(element);
|
| + } else if (__gCrWeb.autofill.isTextAreaElement(element)) {
|
| + // Nothing more to do in this case.
|
| + } else if (extractMask & __gCrWeb.autofill.EXTRACT_MASK_OPTIONS) {
|
| + __gCrWeb.autofill.getOptionStringsFromElement(element, field);
|
| + }
|
| +
|
| + if (!extractMask & __gCrWeb.autofill.EXTRACT_MASK_VALUE) {
|
| + return;
|
| + }
|
| +
|
| + var value = __gCrWeb.autofill.value(element);
|
| +
|
| + if (!__gCrWeb.autofill.isAutofillableInputElement(element)) {
|
| + // Convert the |select_element| value to text if requested.
|
| + if (extractMask & __gCrWeb.autofill.EXTRACT_MASK_OPTION_TEXT) {
|
| + var options = element.options;
|
| + for (var index = 0; index < options.length; ++index) {
|
| + var optionElement = options[index];
|
| + if (__gCrWeb.autofill.value(optionElement) === value) {
|
| + value = optionElement.text;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + // There is a constraint on the maximum data length in method
|
| + // WebFormControlElementToFormField() in form_autofill_util.h in order to
|
| + // prevent a malicious site from DOS'ing the browser: http://crbug.com/49332,
|
| + // which isn't really meaningful here, but we need to follow the same logic to
|
| + // get the same form signature wherever possible (to get the benefits of the
|
| + // existing crowdsourced field detection corpus).
|
| + if (value.length > __gCrWeb.autofill.MAX_DATA_LENGTH) {
|
| + value = value.substr(0, __gCrWeb.autofill.MAX_DATA_LENGTH);
|
| + }
|
| + field['value'] = value;
|
| +};
|
| +
|
| +/**
|
| + * For debugging purposes, annotate forms on the page with prediction data using
|
| + * the placeholder attribute.
|
| + *
|
| + * @param {Object} data The form and field identifiers with their prection data.
|
| + */
|
| +__gCrWeb.autofill['fillPredictionData'] = function(data) {
|
| +
|
| + for (formName in data) {
|
| + var form = __gCrWeb.common.getFormElementFromIdentifier(formName);
|
| + var formData = data[formName];
|
| + var controlElements = __gCrWeb.common.getFormControlElements(form);
|
| + for (var i = 0; i < controlElements.length; ++i) {
|
| + var element = controlElements[i];
|
| + if (!__gCrWeb.autofill.isAutofillableElement(element)) {
|
| + continue;
|
| + }
|
| + var elementName = __gCrWeb['common'].nameForAutofill(element);
|
| + var value = formData[elementName];
|
| + if (value) {
|
| + element.placeholder = value;
|
| + }
|
| + }
|
| + }
|
| +};
|
|
|