diff options
Diffstat (limited to 'chromium/components/autofill/ios/fill/resources/fill.js')
-rw-r--r-- | chromium/components/autofill/ios/fill/resources/fill.js | 177 |
1 files changed, 128 insertions, 49 deletions
diff --git a/chromium/components/autofill/ios/fill/resources/fill.js b/chromium/components/autofill/ios/fill/resources/fill.js index 28ee9065532..0baae455648 100644 --- a/chromium/components/autofill/ios/fill/resources/fill.js +++ b/chromium/components/autofill/ios/fill/resources/fill.js @@ -191,8 +191,8 @@ function setInputElementAngularValue_(value, input) { } /** - * Sets the value of an input and dispatches a change event if - * |shouldSendChangeEvent|. + * Sets the value of an input, dispatches the events on the changed element and + * call |callback| if it is defined. * * It is based on the logic in * @@ -207,43 +207,119 @@ function setInputElementAngularValue_(value, input) { * * @param {string} value The value the input element will be set. * @param {Element} input The input element of which the value is set. - * @param {function(boolean)=} callback Callback function with a boolean + * @param {function()=} callback Callback function with a boolean * argument that indicates if the input element's value was changed. */ __gCrWeb.fill.setInputElementValue = function( value, input, callback = undefined) { - if (!input) { + if (!input) return; + + var activeElement = document.activeElement; + if (input != activeElement) { + __gCrWeb.fill.createAndDispatchHTMLEvent( + activeElement, value, 'blur', true, false); + __gCrWeb.fill.createAndDispatchHTMLEvent( + input, value, 'focus', true, false); } - var changed = false; - if (input.type === 'checkbox' || input.type === 'radio') { - changed = input.checked !== value; - input.checked = value; - } else if (input.type === 'select-one') { - changed = input.value !== value; - input.value = value; - } else { + + setInputElementValue_(value, input); + if (callback) + callback(); + + if (input != activeElement) { + __gCrWeb.fill.createAndDispatchHTMLEvent(input, value, 'blur', true, false); + __gCrWeb.fill.createAndDispatchHTMLEvent( + activeElement, value, 'focus', true, false); + } +}; + +/** + * Internal function to set the element value. + * + * @param {string} value The value the input element will be set. + * @param {Element} input The input element of which the value is set. + */ +function setInputElementValue_(value, input) { + var propertyName = (input.type === 'checkbox' || input.type === 'radio') ? + 'checked' : + 'value'; + if (input.type !== 'select-one' && input.type !== 'checkbox' && + input.type !== 'radio') { // In HTMLInputElement.cpp there is a check on canSetValue(value), which // returns false only for file input. As file input is not relevant for // autofill and this method is only used for autofill for now, there is no // such check in this implementation. - var sanitizedValue = - __gCrWeb.fill.sanitizeValueForInputElement(value, input); - changed = sanitizedValue !== input.value; - input.value = sanitizedValue; + value = __gCrWeb.fill.sanitizeValueForInputElement(value, input); } + + // Return early if the value hasn't changed. + if (input[propertyName] == value) + return; + + // When the user inputs a value in an HTMLInput field, the property setter is + // not called. The different frameworks often call it explicitly when + // receiving the input event. + // This is probably due to the sync between the HTML object and the DOM + // object. + // The sequence of event is: User input -> input event -> setter. + // When the property is set programmatically (input.value = 'foo'), the setter + // is called immediately (then probably called again on the input event) + // JS input -> setter. + // The only way to emulate the user behavior is to override the property + // The getter will return the new value to emulate the fact the the HTML + // value was updated without calling the setter. + // The setter simply forwards the set to the older property descriptor. + // Once the setter has been called, just forward get and set calls. + + var oldPropertyDescriptor = /** @type {!Object} */ ( + Object.getOwnPropertyDescriptor(input, propertyName)); + var overrideProperty = + oldPropertyDescriptor && oldPropertyDescriptor.configurable; + var setterCalled = false; + + if (overrideProperty) { + var newProperty = { + get: function() { + if (setterCalled && oldPropertyDescriptor.get) { + return oldPropertyDescriptor.get.call(input); + } + // Simulate the fact that the HTML value has been set but not yet the + // property. + return value + ''; + }, + configurable: true + }; + if (oldPropertyDescriptor.set) { + newProperty.set = function(e) { + setterCalled = true; + oldPropertyDescriptor.set.call(input, value); + } + } + Object.defineProperty(input, propertyName, newProperty); + } else { + setterCalled = true; + input[propertyName] = value; + } + if (window['angular']) { // The page uses the AngularJS framework. Update the angular value before // sending events. setInputElementAngularValue_(value, input); } - if (changed) { - __gCrWeb.fill.notifyElementValueChanged(input); - } - if (callback) { - callback(changed); + __gCrWeb.fill.notifyElementValueChanged(input, value); + + if (overrideProperty) { + Object.defineProperty(input, propertyName, oldPropertyDescriptor); + if (!setterCalled && input[propertyName] != value) { + // The setter was never called. This may be intentional (the framework + // ignored the input event) or not (the event did not conform to what + // framework expected). The whole function will likely fail, but try to + // set the value directly as a last try. + input[propertyName] = value; + } } -}; +} /** * Returns a sanitized value of proposedValue for a given input element type. @@ -384,15 +460,23 @@ __gCrWeb.fill.sanitizeValueForNumberInputType = function(proposedValue) { /** * Creates and sends notification that element has changed. * - * Most handlers react to 'change' or 'input' event, so sent both. + * Send events that 'mimic' the user typing in a field. + * 'input' event is often use in case of a text field, and 'change'event is + * more often used in case of selects. * * @param {Element} element The element that changed. */ -__gCrWeb.fill.notifyElementValueChanged = function(element) { - __gCrWeb.fill.createAndDispatchHTMLEvent(element, 'keydown', true, false); - __gCrWeb.fill.createAndDispatchHTMLEvent(element, 'change', true, false); - __gCrWeb.fill.createAndDispatchHTMLEvent(element, 'input', true, false); - __gCrWeb.fill.createAndDispatchHTMLEvent(element, 'keyup', true, false); +__gCrWeb.fill.notifyElementValueChanged = function(element, value) { + __gCrWeb.fill.createAndDispatchHTMLEvent( + element, value, 'keydown', true, false); + __gCrWeb.fill.createAndDispatchHTMLEvent( + element, value, 'keypress', true, false); + __gCrWeb.fill.createAndDispatchHTMLEvent( + element, value, 'input', true, false); + __gCrWeb.fill.createAndDispatchHTMLEvent( + element, value, 'keyup', true, false); + __gCrWeb.fill.createAndDispatchHTMLEvent( + element, value, 'change', true, false); }; /** @@ -406,30 +490,25 @@ __gCrWeb.fill.notifyElementValueChanged = function(element) { * canceled. */ __gCrWeb.fill.createAndDispatchHTMLEvent = function( - element, type, bubbles, cancelable) { - var changeEvent = - /** @type {!Event} */ (element.ownerDocument.createEvent('HTMLEvents')); - changeEvent.initEvent(type, bubbles, cancelable); - // Some frameworks will use the data field to update their cache value. - changeEvent.data = element.value; - - // Adding a |simulated| flag on the event will force the React framework to - // update the backend store. - changeEvent.simulated = true; - - element.dispatchEvent(changeEvent); + element, value, type, bubbles, cancelable) { + var event = + new Event(type, {bubbles: bubbles, cancelable: cancelable, data: value}); + if (type == 'input') { + event.inputType = 'insertText'; + } + element.dispatchEvent(event); }; - /** - * Returns a canonical action for |formElement|. It works the same as upstream - * function GetCanonicalActionForForm. - * @param {HTMLFormElement} formElement - * @return {string} Canonical action. - */ +/** + * Returns a canonical action for |formElement|. It works the same as upstream + * function GetCanonicalActionForForm. + * @param {HTMLFormElement} formElement + * @return {string} Canonical action. + */ __gCrWeb.fill.getCanonicalActionForForm = function(formElement) { - var rawAction = formElement.getAttribute('action') || ""; - var absoluteUrl = __gCrWeb.common.absoluteURL( - formElement.ownerDocument, rawAction); + var rawAction = formElement.getAttribute('action') || ''; + var absoluteUrl = + __gCrWeb.common.absoluteURL(formElement.ownerDocument, rawAction); return __gCrWeb.common.removeQueryAndReferenceFromURL(absoluteUrl); }; |