| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All |
| * rights reserved. |
| * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
| * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * Copyright (C) 2012 Samsung Electronics. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "core/html/HTMLInputElement.h" |
| |
| #include "bindings/core/v8/ExceptionMessages.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/ScriptEventListener.h" |
| #include "core/CSSPropertyNames.h" |
| #include "core/HTMLNames.h" |
| #include "core/InputTypeNames.h" |
| #include "core/dom/AXObjectCache.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/ExecutionContextTask.h" |
| #include "core/dom/IdTargetObserver.h" |
| #include "core/dom/StyleChangeReason.h" |
| #include "core/dom/shadow/InsertionPoint.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/spellcheck/SpellChecker.h" |
| #include "core/events/BeforeTextInsertedEvent.h" |
| #include "core/events/KeyboardEvent.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/events/ScopedEventQueue.h" |
| #include "core/frame/Deprecation.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/html/HTMLCollection.h" |
| #include "core/html/HTMLDataListElement.h" |
| #include "core/html/HTMLDataListOptionsCollection.h" |
| #include "core/html/HTMLFormElement.h" |
| #include "core/html/HTMLImageLoader.h" |
| #include "core/html/HTMLOptionElement.h" |
| #include "core/html/forms/ColorChooser.h" |
| #include "core/html/forms/DateTimeChooser.h" |
| #include "core/html/forms/FileInputType.h" |
| #include "core/html/forms/FormController.h" |
| #include "core/html/forms/InputType.h" |
| #include "core/html/forms/SearchInputType.h" |
| #include "core/html/parser/HTMLParserIdioms.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "core/page/ChromeClient.h" |
| #include "platform/Language.h" |
| #include "platform/PlatformMouseEvent.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/text/PlatformLocale.h" |
| #include "wtf/MathExtras.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| class ListAttributeTargetObserver : public IdTargetObserver { |
| public: |
| static ListAttributeTargetObserver* create(const AtomicString& id, |
| HTMLInputElement*); |
| DECLARE_VIRTUAL_TRACE(); |
| void idTargetChanged() override; |
| |
| private: |
| ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement*); |
| |
| Member<HTMLInputElement> m_element; |
| }; |
| |
| // FIXME: According to HTML4, the length attribute's value can be arbitrarily |
| // large. However, due to https://bugs.webkit.org/show_bug.cgi?id=14536 things |
| // get rather sluggish when a text field has a larger number of characters than |
| // this, even when just clicking in the text field. |
| const int HTMLInputElement::maximumLength = 524288; |
| const int defaultSize = 20; |
| |
| HTMLInputElement::HTMLInputElement(Document& document, |
| HTMLFormElement* form, |
| bool createdByParser) |
| : HTMLTextFormControlElement(inputTag, document, form), |
| m_size(defaultSize), |
| m_maxLength(maximumLength), |
| m_minLength(-1), |
| m_hasDirtyValue(false), |
| m_isChecked(false), |
| m_dirtyCheckedness(false), |
| m_isIndeterminate(false), |
| m_isActivatedSubmit(false), |
| m_autocomplete(Uninitialized), |
| m_hasNonEmptyList(false), |
| m_stateRestored(false), |
| m_parsingInProgress(createdByParser), |
| m_valueAttributeWasUpdatedAfterParsing(false), |
| m_canReceiveDroppedFiles(false), |
| m_shouldRevealPassword(false), |
| m_needsToUpdateViewValue(true), |
| m_isPlaceholderVisible(false), |
| // |m_inputType| is lazily created when constructed by the parser to avoid |
| // constructing unnecessarily a text inputType and its shadow subtree, |
| // just to destroy them when the |type| attribute gets set by the parser |
| // to something else than 'text'. |
| m_inputType(createdByParser ? nullptr : InputType::createText(*this)), |
| m_inputTypeView(m_inputType ? m_inputType->createView() : nullptr) { |
| setHasCustomStyleCallbacks(); |
| } |
| |
| HTMLInputElement* HTMLInputElement::create(Document& document, |
| HTMLFormElement* form, |
| bool createdByParser) { |
| HTMLInputElement* inputElement = |
| new HTMLInputElement(document, form, createdByParser); |
| if (!createdByParser) |
| inputElement->ensureUserAgentShadowRoot(); |
| return inputElement; |
| } |
| |
| DEFINE_TRACE(HTMLInputElement) { |
| visitor->trace(m_inputType); |
| visitor->trace(m_inputTypeView); |
| visitor->trace(m_listAttributeTargetObserver); |
| visitor->trace(m_imageLoader); |
| HTMLTextFormControlElement::trace(visitor); |
| } |
| |
| HTMLImageLoader& HTMLInputElement::ensureImageLoader() { |
| if (!m_imageLoader) |
| m_imageLoader = HTMLImageLoader::create(this); |
| return *m_imageLoader; |
| } |
| |
| void HTMLInputElement::didAddUserAgentShadowRoot(ShadowRoot&) { |
| m_inputTypeView->createShadowSubtree(); |
| } |
| |
| HTMLInputElement::~HTMLInputElement() {} |
| |
| const AtomicString& HTMLInputElement::name() const { |
| return m_name.isNull() ? emptyAtom : m_name; |
| } |
| |
| Vector<FileChooserFileInfo> |
| HTMLInputElement::filesFromFileInputFormControlState( |
| const FormControlState& state) { |
| return FileInputType::filesFromFormControlState(state); |
| } |
| |
| bool HTMLInputElement::shouldAutocomplete() const { |
| if (m_autocomplete != Uninitialized) |
| return m_autocomplete == On; |
| return HTMLTextFormControlElement::shouldAutocomplete(); |
| } |
| |
| bool HTMLInputElement::isValidValue(const String& value) const { |
| if (!m_inputType->canSetStringValue()) { |
| NOTREACHED(); |
| return false; |
| } |
| return !m_inputType->typeMismatchFor(value) && |
| !m_inputType->stepMismatch(value) && |
| !m_inputType->rangeUnderflow(value) && |
| !m_inputType->rangeOverflow(value) && |
| !tooLong(value, IgnoreDirtyFlag) && |
| !tooShort(value, IgnoreDirtyFlag) && |
| !m_inputType->patternMismatch(value) && |
| !m_inputType->valueMissing(value); |
| } |
| |
| bool HTMLInputElement::tooLong() const { |
| return willValidate() && tooLong(value(), CheckDirtyFlag); |
| } |
| |
| bool HTMLInputElement::tooShort() const { |
| return willValidate() && tooShort(value(), CheckDirtyFlag); |
| } |
| |
| bool HTMLInputElement::typeMismatch() const { |
| return willValidate() && m_inputType->typeMismatch(); |
| } |
| |
| bool HTMLInputElement::valueMissing() const { |
| return willValidate() && m_inputType->valueMissing(value()); |
| } |
| |
| bool HTMLInputElement::hasBadInput() const { |
| return willValidate() && m_inputTypeView->hasBadInput(); |
| } |
| |
| bool HTMLInputElement::patternMismatch() const { |
| return willValidate() && m_inputType->patternMismatch(value()); |
| } |
| |
| bool HTMLInputElement::tooLong(const String& value, |
| NeedsToCheckDirtyFlag check) const { |
| return m_inputType->tooLong(value, check); |
| } |
| |
| bool HTMLInputElement::tooShort(const String& value, |
| NeedsToCheckDirtyFlag check) const { |
| return m_inputType->tooShort(value, check); |
| } |
| |
| bool HTMLInputElement::rangeUnderflow() const { |
| return willValidate() && m_inputType->rangeUnderflow(value()); |
| } |
| |
| bool HTMLInputElement::rangeOverflow() const { |
| return willValidate() && m_inputType->rangeOverflow(value()); |
| } |
| |
| String HTMLInputElement::validationMessage() const { |
| if (!willValidate()) |
| return String(); |
| |
| if (customError()) |
| return customValidationMessage(); |
| |
| return m_inputType->validationMessage(*m_inputTypeView).first; |
| } |
| |
| String HTMLInputElement::validationSubMessage() const { |
| if (!willValidate() || customError()) |
| return String(); |
| return m_inputType->validationMessage(*m_inputTypeView).second; |
| } |
| |
| double HTMLInputElement::minimum() const { |
| return m_inputType->minimum(); |
| } |
| |
| double HTMLInputElement::maximum() const { |
| return m_inputType->maximum(); |
| } |
| |
| bool HTMLInputElement::stepMismatch() const { |
| return willValidate() && m_inputType->stepMismatch(value()); |
| } |
| |
| bool HTMLInputElement::getAllowedValueStep(Decimal* step) const { |
| return m_inputType->getAllowedValueStep(step); |
| } |
| |
| StepRange HTMLInputElement::createStepRange( |
| AnyStepHandling anyStepHandling) const { |
| return m_inputType->createStepRange(anyStepHandling); |
| } |
| |
| Decimal HTMLInputElement::findClosestTickMarkValue(const Decimal& value) { |
| return m_inputType->findClosestTickMarkValue(value); |
| } |
| |
| void HTMLInputElement::stepUp(int n, ExceptionState& exceptionState) { |
| m_inputType->stepUp(n, exceptionState); |
| } |
| |
| void HTMLInputElement::stepDown(int n, ExceptionState& exceptionState) { |
| m_inputType->stepUp(-n, exceptionState); |
| } |
| |
| void HTMLInputElement::blur() { |
| m_inputTypeView->blur(); |
| } |
| |
| void HTMLInputElement::defaultBlur() { |
| HTMLTextFormControlElement::blur(); |
| } |
| |
| bool HTMLInputElement::hasCustomFocusLogic() const { |
| return m_inputTypeView->hasCustomFocusLogic(); |
| } |
| |
| bool HTMLInputElement::isKeyboardFocusable() const { |
| return m_inputType->isKeyboardFocusable(); |
| } |
| |
| bool HTMLInputElement::shouldShowFocusRingOnMouseFocus() const { |
| return m_inputType->shouldShowFocusRingOnMouseFocus(); |
| } |
| |
| void HTMLInputElement::updateFocusAppearance( |
| SelectionBehaviorOnFocus selectionBehavior) { |
| if (isTextField()) { |
| switch (selectionBehavior) { |
| case SelectionBehaviorOnFocus::Reset: |
| select(); |
| break; |
| case SelectionBehaviorOnFocus::Restore: |
| restoreCachedSelection(); |
| break; |
| case SelectionBehaviorOnFocus::None: |
| return; |
| } |
| // TODO(tkent): scrollRectToVisible is a workaround of a bug of |
| // FrameSelection::revealSelection(). It doesn't scroll correctly in a |
| // case of RangeSelection. crbug.com/443061. |
| if (layoutObject()) |
| layoutObject()->scrollRectToVisible(boundingBox()); |
| if (document().frame()) { |
| // TODO(xiaochengh): The use of |
| // updateStyleAndLayoutIgnorePendingStylesheets needs to be audited. See |
| // http://crbug.com/590369 for more details. |
| document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| document().frame()->selection().revealSelection(); |
| } |
| } else { |
| HTMLTextFormControlElement::updateFocusAppearance(selectionBehavior); |
| } |
| } |
| |
| void HTMLInputElement::beginEditing() { |
| DCHECK(document().isActive()); |
| if (!document().isActive()) |
| return; |
| |
| if (!isTextField()) |
| return; |
| |
| document().frame()->spellChecker().didBeginEditing(this); |
| } |
| |
| void HTMLInputElement::endEditing() { |
| DCHECK(document().isActive()); |
| if (!document().isActive()) |
| return; |
| |
| if (!isTextField()) |
| return; |
| |
| LocalFrame* frame = document().frame(); |
| frame->spellChecker().didEndEditingOnTextField(this); |
| frame->host()->chromeClient().didEndEditingOnTextField(*this); |
| } |
| |
| void HTMLInputElement::handleFocusEvent(Element* oldFocusedElement, |
| WebFocusType type) { |
| m_inputTypeView->handleFocusEvent(oldFocusedElement, type); |
| m_inputType->enableSecureTextInput(); |
| } |
| |
| void HTMLInputElement::dispatchFocusInEvent( |
| const AtomicString& eventType, |
| Element* oldFocusedElement, |
| WebFocusType type, |
| InputDeviceCapabilities* sourceCapabilities) { |
| if (eventType == EventTypeNames::DOMFocusIn) |
| m_inputTypeView->handleFocusInEvent(oldFocusedElement, type); |
| HTMLFormControlElementWithState::dispatchFocusInEvent( |
| eventType, oldFocusedElement, type, sourceCapabilities); |
| } |
| |
| void HTMLInputElement::handleBlurEvent() { |
| m_inputType->disableSecureTextInput(); |
| m_inputTypeView->handleBlurEvent(); |
| } |
| |
| void HTMLInputElement::setType(const AtomicString& type) { |
| setAttribute(typeAttr, type); |
| } |
| |
| void HTMLInputElement::initializeTypeInParsing() { |
| DCHECK(m_parsingInProgress); |
| DCHECK(!m_inputType); |
| DCHECK(!m_inputTypeView); |
| |
| const AtomicString& newTypeName = |
| InputType::normalizeTypeName(fastGetAttribute(typeAttr)); |
| m_inputType = InputType::create(*this, newTypeName); |
| m_inputTypeView = m_inputType->createView(); |
| ensureUserAgentShadowRoot(); |
| |
| setNeedsWillValidateCheck(); |
| |
| m_inputType->warnIfValueIsInvalid(fastGetAttribute(valueAttr).getString()); |
| |
| m_inputTypeView->updateView(); |
| setTextAsOfLastFormControlChangeEvent(value()); |
| setChangedSinceLastFormControlChangeEvent(false); |
| } |
| |
| void HTMLInputElement::updateType() { |
| DCHECK(m_inputType); |
| DCHECK(m_inputTypeView); |
| |
| const AtomicString& newTypeName = |
| InputType::normalizeTypeName(fastGetAttribute(typeAttr)); |
| if (m_inputType->formControlType() == newTypeName) |
| return; |
| |
| InputType* newType = InputType::create(*this, newTypeName); |
| removeFromRadioButtonGroup(); |
| |
| bool didStoreValue = m_inputType->storesValueSeparateFromAttribute(); |
| bool didRespectHeightAndWidth = |
| m_inputType->shouldRespectHeightAndWidthAttributes(); |
| bool couldBeSuccessfulSubmitButton = canBeSuccessfulSubmitButton(); |
| |
| m_inputTypeView->destroyShadowSubtree(); |
| lazyReattachIfAttached(); |
| |
| m_inputType = newType; |
| m_inputTypeView = m_inputType->createView(); |
| m_inputTypeView->createShadowSubtree(); |
| |
| setNeedsWillValidateCheck(); |
| |
| bool willStoreValue = m_inputType->storesValueSeparateFromAttribute(); |
| |
| if (didStoreValue && !willStoreValue && hasDirtyValue()) { |
| setAttribute(valueAttr, AtomicString(m_valueIfDirty)); |
| m_valueIfDirty = String(); |
| m_hasDirtyValue = false; |
| } |
| if (!didStoreValue && willStoreValue) { |
| AtomicString valueString = fastGetAttribute(valueAttr); |
| m_inputType->warnIfValueIsInvalid(valueString); |
| m_valueIfDirty = sanitizeValue(valueString); |
| m_hasDirtyValue = !m_valueIfDirty.isNull(); |
| } else { |
| if (!hasDirtyValue()) |
| m_inputType->warnIfValueIsInvalid( |
| fastGetAttribute(valueAttr).getString()); |
| updateValueIfNeeded(); |
| } |
| |
| m_needsToUpdateViewValue = true; |
| m_inputTypeView->updateView(); |
| |
| if (didRespectHeightAndWidth != |
| m_inputType->shouldRespectHeightAndWidthAttributes()) { |
| DCHECK(elementData()); |
| AttributeCollection attributes = attributesWithoutUpdate(); |
| if (const Attribute* height = attributes.find(heightAttr)) |
| HTMLTextFormControlElement::attributeChanged(heightAttr, height->value(), |
| height->value()); |
| if (const Attribute* width = attributes.find(widthAttr)) |
| HTMLTextFormControlElement::attributeChanged(widthAttr, width->value(), |
| width->value()); |
| if (const Attribute* align = attributes.find(alignAttr)) |
| HTMLTextFormControlElement::attributeChanged(alignAttr, align->value(), |
| align->value()); |
| } |
| |
| if (document().focusedElement() == this) |
| document().updateFocusAppearanceSoon(SelectionBehaviorOnFocus::Restore); |
| |
| setTextAsOfLastFormControlChangeEvent(value()); |
| setChangedSinceLastFormControlChangeEvent(false); |
| |
| addToRadioButtonGroup(); |
| |
| setNeedsValidityCheck(); |
| if ((couldBeSuccessfulSubmitButton || canBeSuccessfulSubmitButton()) && |
| formOwner() && isConnected()) |
| formOwner()->invalidateDefaultButtonStyle(); |
| notifyFormStateChanged(); |
| } |
| |
| void HTMLInputElement::subtreeHasChanged() { |
| m_inputTypeView->subtreeHasChanged(); |
| // When typing in an input field, childrenChanged is not called, so we need to |
| // force the directionality check. |
| calculateAndAdjustDirectionality(); |
| } |
| |
| const AtomicString& HTMLInputElement::formControlType() const { |
| return m_inputType->formControlType(); |
| } |
| |
| bool HTMLInputElement::shouldSaveAndRestoreFormControlState() const { |
| if (!m_inputType->shouldSaveAndRestoreFormControlState()) |
| return false; |
| return HTMLTextFormControlElement::shouldSaveAndRestoreFormControlState(); |
| } |
| |
| FormControlState HTMLInputElement::saveFormControlState() const { |
| return m_inputTypeView->saveFormControlState(); |
| } |
| |
| void HTMLInputElement::restoreFormControlState(const FormControlState& state) { |
| m_inputTypeView->restoreFormControlState(state); |
| m_stateRestored = true; |
| } |
| |
| bool HTMLInputElement::canStartSelection() const { |
| if (!isTextField()) |
| return false; |
| return HTMLTextFormControlElement::canStartSelection(); |
| } |
| |
| int HTMLInputElement::selectionStartForBinding( |
| ExceptionState& exceptionState) const { |
| if (!m_inputType->supportsSelectionAPI()) { |
| UseCounter::count(document(), UseCounter::InputSelectionGettersThrow); |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return 0; |
| } |
| return HTMLTextFormControlElement::selectionStart(); |
| } |
| |
| int HTMLInputElement::selectionEndForBinding( |
| ExceptionState& exceptionState) const { |
| if (!m_inputType->supportsSelectionAPI()) { |
| UseCounter::count(document(), UseCounter::InputSelectionGettersThrow); |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return 0; |
| } |
| return HTMLTextFormControlElement::selectionEnd(); |
| } |
| |
| String HTMLInputElement::selectionDirectionForBinding( |
| ExceptionState& exceptionState) const { |
| if (!m_inputType->supportsSelectionAPI()) { |
| UseCounter::count(document(), UseCounter::InputSelectionGettersThrow); |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return String(); |
| } |
| return HTMLTextFormControlElement::selectionDirection(); |
| } |
| |
| void HTMLInputElement::setSelectionStartForBinding( |
| int start, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| HTMLTextFormControlElement::setSelectionStart(start); |
| } |
| |
| void HTMLInputElement::setSelectionEndForBinding( |
| int end, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| HTMLTextFormControlElement::setSelectionEnd(end); |
| } |
| |
| void HTMLInputElement::setSelectionDirectionForBinding( |
| const String& direction, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| HTMLTextFormControlElement::setSelectionDirection(direction); |
| } |
| |
| void HTMLInputElement::setSelectionRangeForBinding( |
| int start, |
| int end, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| HTMLTextFormControlElement::setSelectionRangeForBinding(start, end); |
| } |
| |
| void HTMLInputElement::setSelectionRangeForBinding( |
| int start, |
| int end, |
| const String& direction, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| HTMLTextFormControlElement::setSelectionRangeForBinding(start, end, |
| direction); |
| } |
| |
| void HTMLInputElement::accessKeyAction(bool sendMouseEvents) { |
| m_inputTypeView->accessKeyAction(sendMouseEvents); |
| } |
| |
| bool HTMLInputElement::isPresentationAttribute( |
| const QualifiedName& name) const { |
| // FIXME: Remove type check. |
| if (name == vspaceAttr || name == hspaceAttr || name == alignAttr || |
| name == widthAttr || name == heightAttr || |
| (name == borderAttr && type() == InputTypeNames::image)) |
| return true; |
| return HTMLTextFormControlElement::isPresentationAttribute(name); |
| } |
| |
| void HTMLInputElement::collectStyleForPresentationAttribute( |
| const QualifiedName& name, |
| const AtomicString& value, |
| MutableStylePropertySet* style) { |
| if (name == vspaceAttr) { |
| addHTMLLengthToStyle(style, CSSPropertyMarginTop, value); |
| addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value); |
| } else if (name == hspaceAttr) { |
| addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value); |
| addHTMLLengthToStyle(style, CSSPropertyMarginRight, value); |
| } else if (name == alignAttr) { |
| if (m_inputType->shouldRespectAlignAttribute()) |
| applyAlignmentAttributeToStyle(value, style); |
| } else if (name == widthAttr) { |
| if (m_inputType->shouldRespectHeightAndWidthAttributes()) |
| addHTMLLengthToStyle(style, CSSPropertyWidth, value); |
| } else if (name == heightAttr) { |
| if (m_inputType->shouldRespectHeightAndWidthAttributes()) |
| addHTMLLengthToStyle(style, CSSPropertyHeight, value); |
| } else if (name == borderAttr && |
| type() == InputTypeNames::image) { // FIXME: Remove type check. |
| applyBorderAttributeToStyle(value, style); |
| } else { |
| HTMLTextFormControlElement::collectStyleForPresentationAttribute( |
| name, value, style); |
| } |
| } |
| |
| void HTMLInputElement::parseAttribute(const QualifiedName& name, |
| const AtomicString& oldValue, |
| const AtomicString& value) { |
| DCHECK(m_inputType); |
| DCHECK(m_inputTypeView); |
| |
| if (name == nameAttr) { |
| removeFromRadioButtonGroup(); |
| m_name = value; |
| addToRadioButtonGroup(); |
| HTMLTextFormControlElement::parseAttribute(name, oldValue, value); |
| } else if (name == autocompleteAttr) { |
| if (equalIgnoringCase(value, "off")) { |
| m_autocomplete = Off; |
| } else { |
| if (value.isEmpty()) |
| m_autocomplete = Uninitialized; |
| else |
| m_autocomplete = On; |
| } |
| } else if (name == typeAttr) { |
| updateType(); |
| } else if (name == valueAttr) { |
| // We only need to setChanged if the form is looking at the default value |
| // right now. |
| if (!hasDirtyValue()) { |
| updatePlaceholderVisibility(); |
| setNeedsStyleRecalc( |
| SubtreeStyleChange, |
| StyleChangeReasonForTracing::fromAttribute(valueAttr)); |
| } |
| m_needsToUpdateViewValue = true; |
| setNeedsValidityCheck(); |
| m_valueAttributeWasUpdatedAfterParsing = !m_parsingInProgress; |
| m_inputType->warnIfValueIsInvalidAndElementIsVisible(value); |
| m_inputTypeView->valueAttributeChanged(); |
| } else if (name == checkedAttr) { |
| // Another radio button in the same group might be checked by state |
| // restore. We shouldn't call setChecked() even if this has the checked |
| // attribute. So, delay the setChecked() call until |
| // finishParsingChildren() is called if parsing is in progress. |
| if ((!m_parsingInProgress || |
| !document().formController().hasFormStates()) && |
| !m_dirtyCheckedness) { |
| setChecked(!value.isNull()); |
| m_dirtyCheckedness = false; |
| } |
| pseudoStateChanged(CSSSelector::PseudoDefault); |
| } else if (name == maxlengthAttr) { |
| parseMaxLengthAttribute(value); |
| } else if (name == minlengthAttr) { |
| parseMinLengthAttribute(value); |
| } else if (name == sizeAttr) { |
| int oldSize = m_size; |
| m_size = defaultSize; |
| int valueAsInteger; |
| if (!value.isEmpty() && parseHTMLInteger(value, valueAsInteger) && |
| valueAsInteger > 0) |
| m_size = valueAsInteger; |
| if (m_size != oldSize && layoutObject()) |
| layoutObject()->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::AttributeChanged); |
| } else if (name == altAttr) { |
| m_inputTypeView->altAttributeChanged(); |
| } else if (name == srcAttr) { |
| m_inputTypeView->srcAttributeChanged(); |
| } else if (name == usemapAttr || name == accesskeyAttr) { |
| // FIXME: ignore for the moment |
| } else if (name == onsearchAttr) { |
| // Search field and slider attributes all just cause updateFromElement to be |
| // called through style recalcing. |
| setAttributeEventListener( |
| EventTypeNames::search, |
| createAttributeEventListener(this, name, value, eventParameterName())); |
| } else if (name == incrementalAttr) { |
| UseCounter::count(document(), UseCounter::IncrementalAttribute); |
| } else if (name == minAttr) { |
| m_inputTypeView->minOrMaxAttributeChanged(); |
| m_inputType->sanitizeValueInResponseToMinOrMaxAttributeChange(); |
| setNeedsValidityCheck(); |
| UseCounter::count(document(), UseCounter::MinAttribute); |
| } else if (name == maxAttr) { |
| m_inputTypeView->minOrMaxAttributeChanged(); |
| m_inputType->sanitizeValueInResponseToMinOrMaxAttributeChange(); |
| setNeedsValidityCheck(); |
| UseCounter::count(document(), UseCounter::MaxAttribute); |
| } else if (name == multipleAttr) { |
| m_inputTypeView->multipleAttributeChanged(); |
| setNeedsValidityCheck(); |
| } else if (name == stepAttr) { |
| m_inputTypeView->stepAttributeChanged(); |
| setNeedsValidityCheck(); |
| UseCounter::count(document(), UseCounter::StepAttribute); |
| } else if (name == patternAttr) { |
| setNeedsValidityCheck(); |
| UseCounter::count(document(), UseCounter::PatternAttribute); |
| } else if (name == readonlyAttr) { |
| HTMLTextFormControlElement::parseAttribute(name, oldValue, value); |
| m_inputTypeView->readonlyAttributeChanged(); |
| } else if (name == listAttr) { |
| m_hasNonEmptyList = !value.isEmpty(); |
| if (m_hasNonEmptyList) { |
| resetListAttributeTargetObserver(); |
| listAttributeTargetChanged(); |
| } |
| UseCounter::count(document(), UseCounter::ListAttribute); |
| } else if (name == webkitdirectoryAttr) { |
| HTMLTextFormControlElement::parseAttribute(name, oldValue, value); |
| UseCounter::count(document(), UseCounter::PrefixedDirectoryAttribute); |
| } else { |
| if (name == formactionAttr) |
| logUpdateAttributeIfIsolatedWorldAndInDocument("input", formactionAttr, |
| oldValue, value); |
| HTMLTextFormControlElement::parseAttribute(name, oldValue, value); |
| } |
| m_inputTypeView->attributeChanged(); |
| } |
| |
| void HTMLInputElement::parserDidSetAttributes() { |
| DCHECK(m_parsingInProgress); |
| initializeTypeInParsing(); |
| } |
| |
| void HTMLInputElement::finishParsingChildren() { |
| m_parsingInProgress = false; |
| DCHECK(m_inputType); |
| DCHECK(m_inputTypeView); |
| HTMLTextFormControlElement::finishParsingChildren(); |
| if (!m_stateRestored) { |
| bool checked = hasAttribute(checkedAttr); |
| if (checked) |
| setChecked(checked); |
| m_dirtyCheckedness = false; |
| } |
| } |
| |
| bool HTMLInputElement::layoutObjectIsNeeded(const ComputedStyle& style) { |
| return m_inputType->layoutObjectIsNeeded() && |
| HTMLTextFormControlElement::layoutObjectIsNeeded(style); |
| } |
| |
| LayoutObject* HTMLInputElement::createLayoutObject(const ComputedStyle& style) { |
| return m_inputTypeView->createLayoutObject(style); |
| } |
| |
| void HTMLInputElement::attachLayoutTree(const AttachContext& context) { |
| HTMLTextFormControlElement::attachLayoutTree(context); |
| |
| m_inputTypeView->startResourceLoading(); |
| m_inputType->countUsage(); |
| |
| if (document().focusedElement() == this) |
| document().updateFocusAppearanceSoon(SelectionBehaviorOnFocus::Restore); |
| } |
| |
| void HTMLInputElement::detachLayoutTree(const AttachContext& context) { |
| HTMLTextFormControlElement::detachLayoutTree(context); |
| m_needsToUpdateViewValue = true; |
| m_inputTypeView->closePopupView(); |
| } |
| |
| String HTMLInputElement::altText() const { |
| // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen |
| // also heavily discussed by Hixie on bugzilla |
| // note this is intentionally different to HTMLImageElement::altText() |
| String alt = fastGetAttribute(altAttr); |
| // fall back to title attribute |
| if (alt.isNull()) |
| alt = fastGetAttribute(titleAttr); |
| if (alt.isNull()) |
| alt = fastGetAttribute(valueAttr); |
| if (alt.isNull()) |
| alt = locale().queryString(WebLocalizedString::InputElementAltText); |
| return alt; |
| } |
| |
| bool HTMLInputElement::canBeSuccessfulSubmitButton() const { |
| return m_inputType->canBeSuccessfulSubmitButton(); |
| } |
| |
| bool HTMLInputElement::isActivatedSubmit() const { |
| return m_isActivatedSubmit; |
| } |
| |
| void HTMLInputElement::setActivatedSubmit(bool flag) { |
| m_isActivatedSubmit = flag; |
| } |
| |
| void HTMLInputElement::appendToFormData(FormData& formData) { |
| if (m_inputType->isFormDataAppendable()) |
| m_inputType->appendToFormData(formData); |
| } |
| |
| String HTMLInputElement::resultForDialogSubmit() { |
| return m_inputType->resultForDialogSubmit(); |
| } |
| |
| void HTMLInputElement::resetImpl() { |
| if (m_inputType->storesValueSeparateFromAttribute()) { |
| setValue(String()); |
| setNeedsValidityCheck(); |
| } |
| |
| setChecked(hasAttribute(checkedAttr)); |
| m_dirtyCheckedness = false; |
| } |
| |
| bool HTMLInputElement::isTextField() const { |
| return m_inputType->isTextField(); |
| } |
| |
| void HTMLInputElement::dispatchChangeEventIfNeeded() { |
| if (isConnected() && m_inputType->shouldSendChangeEventAfterCheckedChanged()) |
| dispatchChangeEvent(); |
| } |
| |
| bool HTMLInputElement::checked() const { |
| m_inputType->readingChecked(); |
| return m_isChecked; |
| } |
| |
| void HTMLInputElement::setChecked(bool nowChecked, |
| TextFieldEventBehavior eventBehavior) { |
| m_dirtyCheckedness = true; |
| if (checked() == nowChecked) |
| return; |
| |
| m_isChecked = nowChecked; |
| |
| if (RadioButtonGroupScope* scope = radioButtonGroupScope()) |
| scope->updateCheckedState(this); |
| if (layoutObject()) |
| LayoutTheme::theme().controlStateChanged(*layoutObject(), |
| CheckedControlState); |
| |
| setNeedsValidityCheck(); |
| |
| // Ideally we'd do this from the layout tree (matching |
| // LayoutTextView), but it's not possible to do it at the moment |
| // because of the way the code is structured. |
| if (layoutObject()) { |
| if (AXObjectCache* cache = |
| layoutObject()->document().existingAXObjectCache()) |
| cache->checkedStateChanged(this); |
| } |
| |
| // Only send a change event for items in the document (avoid firing during |
| // parsing) and don't send a change event for a radio button that's getting |
| // unchecked to match other browsers. DOM is not a useful standard for this |
| // because it says only to fire change events at "lose focus" time, which is |
| // definitely wrong in practice for these types of elements. |
| if (eventBehavior != DispatchNoEvent && isConnected() && |
| m_inputType->shouldSendChangeEventAfterCheckedChanged()) { |
| setTextAsOfLastFormControlChangeEvent(String()); |
| if (eventBehavior == DispatchInputAndChangeEvent) |
| dispatchFormControlInputEvent(); |
| } |
| |
| pseudoStateChanged(CSSSelector::PseudoChecked); |
| } |
| |
| void HTMLInputElement::setIndeterminate(bool newValue) { |
| if (indeterminate() == newValue) |
| return; |
| |
| m_isIndeterminate = newValue; |
| |
| pseudoStateChanged(CSSSelector::PseudoIndeterminate); |
| |
| if (layoutObject()) |
| LayoutTheme::theme().controlStateChanged(*layoutObject(), |
| CheckedControlState); |
| } |
| |
| int HTMLInputElement::size() const { |
| return m_size; |
| } |
| |
| bool HTMLInputElement::sizeShouldIncludeDecoration(int& preferredSize) const { |
| return m_inputTypeView->sizeShouldIncludeDecoration(defaultSize, |
| preferredSize); |
| } |
| |
| void HTMLInputElement::copyNonAttributePropertiesFromElement( |
| const Element& source) { |
| const HTMLInputElement& sourceElement = |
| static_cast<const HTMLInputElement&>(source); |
| |
| m_valueIfDirty = sourceElement.m_valueIfDirty; |
| m_hasDirtyValue = sourceElement.m_hasDirtyValue; |
| setChecked(sourceElement.m_isChecked); |
| m_dirtyCheckedness = sourceElement.m_dirtyCheckedness; |
| m_isIndeterminate = sourceElement.m_isIndeterminate; |
| |
| HTMLTextFormControlElement::copyNonAttributePropertiesFromElement(source); |
| |
| m_needsToUpdateViewValue = true; |
| m_inputTypeView->updateView(); |
| } |
| |
| String HTMLInputElement::value() const { |
| String value; |
| if (m_inputType->getTypeSpecificValue(value)) |
| return value; |
| |
| if (hasDirtyValue()) |
| return m_valueIfDirty; |
| |
| AtomicString valueString = fastGetAttribute(valueAttr); |
| value = sanitizeValue(valueString); |
| if (!value.isNull()) |
| return value; |
| |
| return m_inputType->fallbackValue(); |
| } |
| |
| String HTMLInputElement::valueWithDefault() const { |
| String value = this->value(); |
| if (!value.isNull()) |
| return value; |
| |
| return m_inputType->defaultValue(); |
| } |
| |
| void HTMLInputElement::setValueForUser(const String& value) { |
| // Call setValue and make it send a change event. |
| setValue(value, DispatchChangeEvent); |
| } |
| |
| const String& HTMLInputElement::suggestedValue() const { |
| return m_suggestedValue; |
| } |
| |
| void HTMLInputElement::setSuggestedValue(const String& value) { |
| if (!m_inputType->canSetSuggestedValue()) |
| return; |
| m_needsToUpdateViewValue = true; |
| m_suggestedValue = sanitizeValue(value); |
| setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create( |
| StyleChangeReason::ControlValue)); |
| m_inputTypeView->updateView(); |
| } |
| |
| void HTMLInputElement::setEditingValue(const String& value) { |
| if (!layoutObject() || !isTextField()) |
| return; |
| setInnerEditorValue(value); |
| subtreeHasChanged(); |
| |
| unsigned max = value.length(); |
| setSelectionRange(max, max); |
| dispatchInputEvent(); |
| } |
| |
| void HTMLInputElement::setInnerEditorValue(const String& value) { |
| HTMLTextFormControlElement::setInnerEditorValue(value); |
| m_needsToUpdateViewValue = false; |
| } |
| |
| void HTMLInputElement::setValue(const String& value, |
| ExceptionState& exceptionState, |
| TextFieldEventBehavior eventBehavior) { |
| // FIXME: Remove type check. |
| if (type() == InputTypeNames::file && !value.isEmpty()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "This input element accepts a filename, " |
| "which may only be programmatically set " |
| "to the empty string."); |
| return; |
| } |
| setValue(value, eventBehavior); |
| } |
| |
| void HTMLInputElement::setValue(const String& value, |
| TextFieldEventBehavior eventBehavior) { |
| m_inputType->warnIfValueIsInvalidAndElementIsVisible(value); |
| if (!m_inputType->canSetValue(value)) |
| return; |
| |
| EventQueueScope scope; |
| String sanitizedValue = sanitizeValue(value); |
| bool valueChanged = sanitizedValue != this->value(); |
| |
| setLastChangeWasNotUserEdit(); |
| m_needsToUpdateViewValue = true; |
| // Prevent TextFieldInputType::setValue from using the suggested value. |
| m_suggestedValue = String(); |
| |
| m_inputType->setValue(sanitizedValue, valueChanged, eventBehavior); |
| m_inputTypeView->didSetValue(sanitizedValue, valueChanged); |
| |
| if (valueChanged && eventBehavior == DispatchNoEvent) |
| setTextAsOfLastFormControlChangeEvent( |
| sanitizedValue.isNull() ? defaultValue() : sanitizedValue); |
| |
| if (!valueChanged) |
| return; |
| |
| notifyFormStateChanged(); |
| } |
| |
| void HTMLInputElement::setValueInternal(const String& sanitizedValue, |
| TextFieldEventBehavior eventBehavior) { |
| m_valueIfDirty = sanitizedValue; |
| m_hasDirtyValue = !m_valueIfDirty.isNull(); |
| setNeedsValidityCheck(); |
| if (m_inputType->isSteppable()) { |
| pseudoStateChanged(CSSSelector::PseudoInRange); |
| pseudoStateChanged(CSSSelector::PseudoOutOfRange); |
| } |
| if (document().focusedElement() == this) |
| document() |
| .frameHost() |
| ->chromeClient() |
| .didUpdateTextOfFocusedElementByNonUserInput(*document().frame()); |
| } |
| |
| bool HTMLInputElement::hasDirtyValue() const { |
| DCHECK_EQ(!m_hasDirtyValue, m_valueIfDirty.isNull()); |
| return m_hasDirtyValue; |
| } |
| |
| void HTMLInputElement::updateView() { |
| m_inputTypeView->updateView(); |
| } |
| |
| double HTMLInputElement::valueAsDate(bool& isNull) const { |
| double date = m_inputType->valueAsDate(); |
| isNull = !std::isfinite(date); |
| return date; |
| } |
| |
| void HTMLInputElement::setValueAsDate(double value, |
| ExceptionState& exceptionState) { |
| m_inputType->setValueAsDate(value, exceptionState); |
| } |
| |
| double HTMLInputElement::valueAsNumber() const { |
| return m_inputType->valueAsDouble(); |
| } |
| |
| void HTMLInputElement::setValueAsNumber(double newValue, |
| ExceptionState& exceptionState, |
| TextFieldEventBehavior eventBehavior) { |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#dom-input-valueasnumber |
| // On setting, if the new value is infinite, then throw a TypeError exception. |
| if (std::isinf(newValue)) { |
| exceptionState.throwTypeError( |
| ExceptionMessages::notAFiniteNumber(newValue)); |
| return; |
| } |
| m_inputType->setValueAsDouble(newValue, eventBehavior, exceptionState); |
| } |
| |
| void HTMLInputElement::setValueFromRenderer(const String& value) { |
| // File upload controls will never use this. |
| DCHECK_NE(type(), InputTypeNames::file); |
| |
| m_suggestedValue = String(); |
| |
| // Renderer and our event handler are responsible for sanitizing values. |
| DCHECK(value == m_inputType->sanitizeUserInputValue(value) || |
| m_inputType->sanitizeUserInputValue(value).isEmpty()); |
| |
| DCHECK(!value.isNull()); |
| m_valueIfDirty = value; |
| m_hasDirtyValue = true; |
| m_needsToUpdateViewValue = false; |
| |
| // Input event is fired by the Node::defaultEventHandler for editable |
| // controls. |
| if (!isTextField()) |
| dispatchInputEvent(); |
| notifyFormStateChanged(); |
| |
| setNeedsValidityCheck(); |
| |
| // Clear autofill flag (and yellow background) on user edit. |
| setAutofilled(false); |
| } |
| |
| EventDispatchHandlingState* HTMLInputElement::preDispatchEventHandler( |
| Event* event) { |
| if (event->type() == EventTypeNames::textInput && |
| m_inputTypeView->shouldSubmitImplicitly(event)) { |
| event->stopPropagation(); |
| return nullptr; |
| } |
| if (event->type() != EventTypeNames::click) |
| return nullptr; |
| if (!event->isMouseEvent() || |
| toMouseEvent(event)->button() != |
| static_cast<short>(WebPointerProperties::Button::Left)) |
| return nullptr; |
| return m_inputTypeView->willDispatchClick(); |
| } |
| |
| void HTMLInputElement::postDispatchEventHandler( |
| Event* event, |
| EventDispatchHandlingState* state) { |
| if (!state) |
| return; |
| m_inputTypeView->didDispatchClick(event, |
| *static_cast<ClickHandlingState*>(state)); |
| } |
| |
| void HTMLInputElement::defaultEventHandler(Event* evt) { |
| if (evt->isMouseEvent() && evt->type() == EventTypeNames::click && |
| toMouseEvent(evt)->button() == |
| static_cast<short>(WebPointerProperties::Button::Left)) { |
| m_inputTypeView->handleClickEvent(toMouseEvent(evt)); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| if (evt->isKeyboardEvent() && evt->type() == EventTypeNames::keydown) { |
| m_inputTypeView->handleKeydownEvent(toKeyboardEvent(evt)); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| // Call the base event handler before any of our own event handling for almost |
| // all events in text fields. Makes editing keyboard handling take precedence |
| // over the keydown and keypress handling in this function. |
| bool callBaseClassEarly = |
| isTextField() && (evt->type() == EventTypeNames::keydown || |
| evt->type() == EventTypeNames::keypress); |
| if (callBaseClassEarly) { |
| HTMLTextFormControlElement::defaultEventHandler(evt); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| // DOMActivate events cause the input to be "activated" - in the case of image |
| // and submit inputs, this means actually submitting the form. For reset |
| // inputs, the form is reset. These events are sent when the user clicks on |
| // the element, or presses enter while it is the active element. JavaScript |
| // code wishing to activate the element must dispatch a DOMActivate event - a |
| // click event will not do the job. |
| if (evt->type() == EventTypeNames::DOMActivate) { |
| m_inputTypeView->handleDOMActivateEvent(evt); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| // Use key press event here since sending simulated mouse events |
| // on key down blocks the proper sending of the key press event. |
| if (evt->isKeyboardEvent() && evt->type() == EventTypeNames::keypress) { |
| m_inputTypeView->handleKeypressEvent(toKeyboardEvent(evt)); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| if (evt->isKeyboardEvent() && evt->type() == EventTypeNames::keyup) { |
| m_inputTypeView->handleKeyupEvent(toKeyboardEvent(evt)); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| if (m_inputTypeView->shouldSubmitImplicitly(evt)) { |
| // FIXME: Remove type check. |
| if (type() == InputTypeNames::search) |
| document().postTask(BLINK_FROM_HERE, |
| createSameThreadTask(&HTMLInputElement::onSearch, |
| wrapPersistent(this))); |
| // Form submission finishes editing, just as loss of focus does. |
| // If there was a change, send the event now. |
| if (wasChangedSinceLastFormControlChangeEvent()) |
| dispatchFormControlChangeEvent(); |
| |
| HTMLFormElement* formForSubmission = m_inputTypeView->formForSubmission(); |
| // Form may never have been present, or may have been destroyed by code |
| // responding to the change event. |
| if (formForSubmission) |
| formForSubmission->submitImplicitly(evt, canTriggerImplicitSubmission()); |
| |
| evt->setDefaultHandled(); |
| return; |
| } |
| |
| if (evt->isBeforeTextInsertedEvent()) |
| m_inputTypeView->handleBeforeTextInsertedEvent( |
| static_cast<BeforeTextInsertedEvent*>(evt)); |
| |
| if (evt->isMouseEvent() && evt->type() == EventTypeNames::mousedown) { |
| m_inputTypeView->handleMouseDownEvent(toMouseEvent(evt)); |
| if (evt->defaultHandled()) |
| return; |
| } |
| |
| m_inputTypeView->forwardEvent(evt); |
| |
| if (!callBaseClassEarly && !evt->defaultHandled()) |
| HTMLTextFormControlElement::defaultEventHandler(evt); |
| } |
| |
| bool HTMLInputElement::willRespondToMouseClickEvents() { |
| // FIXME: Consider implementing willRespondToMouseClickEvents() in InputType |
| // if more accurate results are necessary. |
| if (!isDisabledFormControl()) |
| return true; |
| |
| return HTMLTextFormControlElement::willRespondToMouseClickEvents(); |
| } |
| |
| bool HTMLInputElement::isURLAttribute(const Attribute& attribute) const { |
| return attribute.name() == srcAttr || attribute.name() == formactionAttr || |
| HTMLTextFormControlElement::isURLAttribute(attribute); |
| } |
| |
| bool HTMLInputElement::hasLegalLinkAttribute(const QualifiedName& name) const { |
| return m_inputType->hasLegalLinkAttribute(name) || |
| HTMLTextFormControlElement::hasLegalLinkAttribute(name); |
| } |
| |
| const QualifiedName& HTMLInputElement::subResourceAttributeName() const { |
| return m_inputType->subResourceAttributeName(); |
| } |
| |
| const AtomicString& HTMLInputElement::defaultValue() const { |
| return fastGetAttribute(valueAttr); |
| } |
| |
| static inline bool isRFC2616TokenCharacter(UChar ch) { |
| return isASCII(ch) && ch > ' ' && ch != '"' && ch != '(' && ch != ')' && |
| ch != ',' && ch != '/' && (ch < ':' || ch > '@') && |
| (ch < '[' || ch > ']') && ch != '{' && ch != '}' && ch != 0x7f; |
| } |
| |
| static bool isValidMIMEType(const String& type) { |
| size_t slashPosition = type.find('/'); |
| if (slashPosition == kNotFound || !slashPosition || |
| slashPosition == type.length() - 1) |
| return false; |
| for (size_t i = 0; i < type.length(); ++i) { |
| if (!isRFC2616TokenCharacter(type[i]) && i != slashPosition) |
| return false; |
| } |
| return true; |
| } |
| |
| static bool isValidFileExtension(const String& type) { |
| if (type.length() < 2) |
| return false; |
| return type[0] == '.'; |
| } |
| |
| static Vector<String> parseAcceptAttribute(const String& acceptString, |
| bool (*predicate)(const String&)) { |
| Vector<String> types; |
| if (acceptString.isEmpty()) |
| return types; |
| |
| Vector<String> splitTypes; |
| acceptString.split(',', false, splitTypes); |
| for (const String& splitType : splitTypes) { |
| String trimmedType = stripLeadingAndTrailingHTMLSpaces(splitType); |
| if (trimmedType.isEmpty()) |
| continue; |
| if (!predicate(trimmedType)) |
| continue; |
| types.append(trimmedType.lower()); |
| } |
| |
| return types; |
| } |
| |
| Vector<String> HTMLInputElement::acceptMIMETypes() { |
| return parseAcceptAttribute(fastGetAttribute(acceptAttr), isValidMIMEType); |
| } |
| |
| Vector<String> HTMLInputElement::acceptFileExtensions() { |
| return parseAcceptAttribute(fastGetAttribute(acceptAttr), |
| isValidFileExtension); |
| } |
| |
| const AtomicString& HTMLInputElement::alt() const { |
| return fastGetAttribute(altAttr); |
| } |
| |
| int HTMLInputElement::maxLength() const { |
| if (!hasAttribute(maxlengthAttr)) |
| return -1; |
| return m_maxLength; |
| } |
| |
| int HTMLInputElement::minLength() const { |
| return m_minLength; |
| } |
| |
| void HTMLInputElement::setMaxLength(int maxLength, |
| ExceptionState& exceptionState) { |
| if (maxLength < 0) |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| "The value provided (" + String::number(maxLength) + ") is negative."); |
| else if (maxLength < m_minLength) |
| exceptionState.throwDOMException( |
| IndexSizeError, ExceptionMessages::indexExceedsMinimumBound( |
| "maxLength", maxLength, m_minLength)); |
| else |
| setIntegralAttribute(maxlengthAttr, maxLength); |
| } |
| |
| void HTMLInputElement::setMinLength(int minLength, |
| ExceptionState& exceptionState) { |
| if (minLength < 0) |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| "The value provided (" + String::number(minLength) + ") is negative."); |
| else if (minLength > m_maxLength) |
| exceptionState.throwDOMException( |
| IndexSizeError, ExceptionMessages::indexExceedsMaximumBound( |
| "minLength", minLength, m_maxLength)); |
| else |
| setIntegralAttribute(minlengthAttr, minLength); |
| } |
| |
| bool HTMLInputElement::multiple() const { |
| return fastHasAttribute(multipleAttr); |
| } |
| |
| void HTMLInputElement::setSize(unsigned size) { |
| setUnsignedIntegralAttribute(sizeAttr, size); |
| } |
| |
| void HTMLInputElement::setSize(unsigned size, ExceptionState& exceptionState) { |
| if (!size) |
| exceptionState.throwDOMException( |
| IndexSizeError, "The value provided is 0, which is an invalid size."); |
| else |
| setSize(size); |
| } |
| |
| KURL HTMLInputElement::src() const { |
| return document().completeURL(fastGetAttribute(srcAttr)); |
| } |
| |
| FileList* HTMLInputElement::files() { |
| return m_inputType->files(); |
| } |
| |
| void HTMLInputElement::setFiles(FileList* files) { |
| m_inputType->setFiles(files); |
| } |
| |
| bool HTMLInputElement::receiveDroppedFiles(const DragData* dragData) { |
| return m_inputType->receiveDroppedFiles(dragData); |
| } |
| |
| String HTMLInputElement::droppedFileSystemId() { |
| return m_inputType->droppedFileSystemId(); |
| } |
| |
| bool HTMLInputElement::canReceiveDroppedFiles() const { |
| return m_canReceiveDroppedFiles; |
| } |
| |
| void HTMLInputElement::setCanReceiveDroppedFiles(bool canReceiveDroppedFiles) { |
| if (!!m_canReceiveDroppedFiles == canReceiveDroppedFiles) |
| return; |
| m_canReceiveDroppedFiles = canReceiveDroppedFiles; |
| if (layoutObject()) |
| layoutObject()->updateFromElement(); |
| } |
| |
| String HTMLInputElement::sanitizeValue(const String& proposedValue) const { |
| if (proposedValue.isNull()) |
| return proposedValue; |
| return m_inputType->sanitizeValue(proposedValue); |
| } |
| |
| String HTMLInputElement::localizeValue(const String& proposedValue) const { |
| if (proposedValue.isNull()) |
| return proposedValue; |
| return m_inputType->localizeValue(proposedValue); |
| } |
| |
| bool HTMLInputElement::isInRange() const { |
| return willValidate() && m_inputType->isInRange(value()); |
| } |
| |
| bool HTMLInputElement::isOutOfRange() const { |
| return willValidate() && m_inputType->isOutOfRange(value()); |
| } |
| |
| bool HTMLInputElement::isRequiredFormControl() const { |
| return m_inputType->supportsRequired() && isRequired(); |
| } |
| |
| bool HTMLInputElement::matchesReadOnlyPseudoClass() const { |
| return m_inputType->supportsReadOnly() && isReadOnly(); |
| } |
| |
| bool HTMLInputElement::matchesReadWritePseudoClass() const { |
| return m_inputType->supportsReadOnly() && !isReadOnly(); |
| } |
| |
| void HTMLInputElement::onSearch() { |
| m_inputType->dispatchSearchEvent(); |
| } |
| |
| void HTMLInputElement::updateClearButtonVisibility() { |
| m_inputTypeView->updateClearButtonVisibility(); |
| } |
| |
| void HTMLInputElement::willChangeForm() { |
| removeFromRadioButtonGroup(); |
| HTMLTextFormControlElement::willChangeForm(); |
| } |
| |
| void HTMLInputElement::didChangeForm() { |
| HTMLTextFormControlElement::didChangeForm(); |
| addToRadioButtonGroup(); |
| } |
| |
| Node::InsertionNotificationRequest HTMLInputElement::insertedInto( |
| ContainerNode* insertionPoint) { |
| HTMLTextFormControlElement::insertedInto(insertionPoint); |
| if (insertionPoint->isConnected() && !form()) |
| addToRadioButtonGroup(); |
| resetListAttributeTargetObserver(); |
| logAddElementIfIsolatedWorldAndInDocument("input", typeAttr, formactionAttr); |
| return InsertionShouldCallDidNotifySubtreeInsertions; |
| } |
| |
| void HTMLInputElement::removedFrom(ContainerNode* insertionPoint) { |
| m_inputTypeView->closePopupView(); |
| if (insertionPoint->isConnected() && !form()) |
| removeFromRadioButtonGroup(); |
| HTMLTextFormControlElement::removedFrom(insertionPoint); |
| DCHECK(!isConnected()); |
| resetListAttributeTargetObserver(); |
| } |
| |
| void HTMLInputElement::didMoveToNewDocument(Document& oldDocument) { |
| if (imageLoader()) |
| imageLoader()->elementDidMoveToNewDocument(); |
| |
| // FIXME: Remove type check. |
| if (type() == InputTypeNames::radio) |
| treeScope().radioButtonGroupScope().removeButton(this); |
| |
| HTMLTextFormControlElement::didMoveToNewDocument(oldDocument); |
| } |
| |
| bool HTMLInputElement::recalcWillValidate() const { |
| return m_inputType->supportsValidation() && |
| HTMLTextFormControlElement::recalcWillValidate(); |
| } |
| |
| void HTMLInputElement::requiredAttributeChanged() { |
| HTMLTextFormControlElement::requiredAttributeChanged(); |
| if (RadioButtonGroupScope* scope = radioButtonGroupScope()) |
| scope->requiredAttributeChanged(this); |
| m_inputTypeView->requiredAttributeChanged(); |
| } |
| |
| void HTMLInputElement::disabledAttributeChanged() { |
| HTMLTextFormControlElement::disabledAttributeChanged(); |
| m_inputTypeView->disabledAttributeChanged(); |
| } |
| |
| void HTMLInputElement::selectColorInColorChooser(const Color& color) { |
| if (ColorChooserClient* client = m_inputType->colorChooserClient()) |
| client->didChooseColor(color); |
| } |
| |
| void HTMLInputElement::endColorChooser() { |
| if (ColorChooserClient* client = m_inputType->colorChooserClient()) |
| client->didEndChooser(); |
| } |
| |
| HTMLElement* HTMLInputElement::list() const { |
| return dataList(); |
| } |
| |
| HTMLDataListElement* HTMLInputElement::dataList() const { |
| if (!m_hasNonEmptyList) |
| return nullptr; |
| |
| if (!m_inputType->shouldRespectListAttribute()) |
| return nullptr; |
| |
| Element* element = treeScope().getElementById(fastGetAttribute(listAttr)); |
| if (!element) |
| return nullptr; |
| if (!isHTMLDataListElement(*element)) |
| return nullptr; |
| |
| return toHTMLDataListElement(element); |
| } |
| |
| bool HTMLInputElement::hasValidDataListOptions() const { |
| HTMLDataListElement* dataList = this->dataList(); |
| if (!dataList) |
| return false; |
| HTMLDataListOptionsCollection* options = dataList->options(); |
| for (unsigned i = 0; HTMLOptionElement* option = options->item(i); ++i) { |
| if (isValidValue(option->value())) |
| return true; |
| } |
| return false; |
| } |
| |
| HeapVector<Member<HTMLOptionElement>> |
| HTMLInputElement::filteredDataListOptions() const { |
| HeapVector<Member<HTMLOptionElement>> filtered; |
| HTMLDataListElement* dataList = this->dataList(); |
| if (!dataList) |
| return filtered; |
| |
| String value = innerEditorValue(); |
| if (multiple() && type() == InputTypeNames::email) { |
| Vector<String> emails; |
| value.split(',', true, emails); |
| if (!emails.isEmpty()) |
| value = emails.last().stripWhiteSpace(); |
| } |
| |
| HTMLDataListOptionsCollection* options = dataList->options(); |
| filtered.reserveCapacity(options->length()); |
| value = value.foldCase(); |
| for (unsigned i = 0; i < options->length(); ++i) { |
| HTMLOptionElement* option = options->item(i); |
| DCHECK(option); |
| if (!value.isEmpty()) { |
| // Firefox shows OPTIONs with matched labels, Edge shows OPTIONs |
| // with matches values. We show both. |
| if (option->value().foldCase().find(value) == kNotFound && |
| option->label().foldCase().find(value) == kNotFound) |
| continue; |
| } |
| // TODO(tkent): Should allow invalid strings. crbug.com/607097. |
| if (!isValidValue(option->value())) |
| continue; |
| filtered.append(option); |
| } |
| return filtered; |
| } |
| |
| void HTMLInputElement::setListAttributeTargetObserver( |
| ListAttributeTargetObserver* newObserver) { |
| if (m_listAttributeTargetObserver) |
| m_listAttributeTargetObserver->unregister(); |
| m_listAttributeTargetObserver = newObserver; |
| } |
| |
| void HTMLInputElement::resetListAttributeTargetObserver() { |
| if (isConnected()) |
| setListAttributeTargetObserver( |
| ListAttributeTargetObserver::create(fastGetAttribute(listAttr), this)); |
| else |
| setListAttributeTargetObserver(nullptr); |
| } |
| |
| void HTMLInputElement::listAttributeTargetChanged() { |
| m_inputTypeView->listAttributeTargetChanged(); |
| } |
| |
| bool HTMLInputElement::isSteppable() const { |
| return m_inputType->isSteppable(); |
| } |
| |
| bool HTMLInputElement::isTextButton() const { |
| return m_inputType->isTextButton(); |
| } |
| |
| bool HTMLInputElement::isEnumeratable() const { |
| return m_inputType->isEnumeratable(); |
| } |
| |
| bool HTMLInputElement::supportLabels() const { |
| return m_inputType->isInteractiveContent(); |
| } |
| |
| bool HTMLInputElement::matchesDefaultPseudoClass() const { |
| return m_inputType->matchesDefaultPseudoClass(); |
| } |
| |
| bool HTMLInputElement::shouldAppearChecked() const { |
| return checked() && m_inputType->isCheckable(); |
| } |
| |
| void HTMLInputElement::setPlaceholderVisibility(bool visible) { |
| m_isPlaceholderVisible = visible; |
| } |
| |
| bool HTMLInputElement::supportsPlaceholder() const { |
| return m_inputType->supportsPlaceholder(); |
| } |
| |
| void HTMLInputElement::updatePlaceholderText() { |
| return m_inputTypeView->updatePlaceholderText(); |
| } |
| |
| void HTMLInputElement::parseMaxLengthAttribute(const AtomicString& value) { |
| int maxLength; |
| if (!parseHTMLInteger(value, maxLength) || maxLength < 0) |
| maxLength = -1; |
| if (maxLength > maximumLength) |
| maxLength = maximumLength; |
| int oldMaxLength = m_maxLength; |
| m_maxLength = maxLength; |
| if (oldMaxLength != maxLength) |
| updateValueIfNeeded(); |
| setNeedsValidityCheck(); |
| } |
| |
| void HTMLInputElement::parseMinLengthAttribute(const AtomicString& value) { |
| int minLength; |
| if (!parseHTMLInteger(value, minLength) || minLength < 0) |
| minLength = -1; |
| m_minLength = minLength; |
| setNeedsValidityCheck(); |
| } |
| |
| void HTMLInputElement::updateValueIfNeeded() { |
| String newValue = sanitizeValue(m_valueIfDirty); |
| DCHECK(!m_valueIfDirty.isNull() || newValue.isNull()); |
| if (newValue != m_valueIfDirty) |
| setValue(newValue); |
| } |
| |
| bool HTMLInputElement::supportsAutocapitalize() const { |
| return m_inputType->supportsAutocapitalize(); |
| } |
| |
| const AtomicString& HTMLInputElement::defaultAutocapitalize() const { |
| return m_inputType->defaultAutocapitalize(); |
| } |
| |
| String HTMLInputElement::defaultToolTip() const { |
| return m_inputType->defaultToolTip(*m_inputTypeView); |
| } |
| |
| bool HTMLInputElement::shouldAppearIndeterminate() const { |
| return m_inputType->shouldAppearIndeterminate(); |
| } |
| |
| bool HTMLInputElement::isInRequiredRadioButtonGroup() { |
| // TODO(tkent): Remove type check. |
| DCHECK_EQ(type(), InputTypeNames::radio); |
| if (RadioButtonGroupScope* scope = radioButtonGroupScope()) |
| return scope->isInRequiredGroup(this); |
| return false; |
| } |
| |
| HTMLInputElement* HTMLInputElement::checkedRadioButtonForGroup() { |
| if (checked()) |
| return this; |
| if (RadioButtonGroupScope* scope = radioButtonGroupScope()) |
| return scope->checkedButtonForGroup(name()); |
| return nullptr; |
| } |
| |
| RadioButtonGroupScope* HTMLInputElement::radioButtonGroupScope() const { |
| // FIXME: Remove type check. |
| if (type() != InputTypeNames::radio) |
| return nullptr; |
| if (HTMLFormElement* formElement = form()) |
| return &formElement->radioButtonGroupScope(); |
| if (isConnected()) |
| return &treeScope().radioButtonGroupScope(); |
| return nullptr; |
| } |
| |
| unsigned HTMLInputElement::sizeOfRadioGroup() const { |
| RadioButtonGroupScope* scope = radioButtonGroupScope(); |
| if (!scope) |
| return 0; |
| return scope->groupSizeFor(this); |
| } |
| |
| inline void HTMLInputElement::addToRadioButtonGroup() { |
| if (RadioButtonGroupScope* scope = radioButtonGroupScope()) |
| scope->addButton(this); |
| } |
| |
| inline void HTMLInputElement::removeFromRadioButtonGroup() { |
| if (RadioButtonGroupScope* scope = radioButtonGroupScope()) |
| scope->removeButton(this); |
| } |
| |
| unsigned HTMLInputElement::height() const { |
| return m_inputType->height(); |
| } |
| |
| unsigned HTMLInputElement::width() const { |
| return m_inputType->width(); |
| } |
| |
| void HTMLInputElement::setHeight(unsigned height) { |
| setUnsignedIntegralAttribute(heightAttr, height); |
| } |
| |
| void HTMLInputElement::setWidth(unsigned width) { |
| setUnsignedIntegralAttribute(widthAttr, width); |
| } |
| |
| ListAttributeTargetObserver* ListAttributeTargetObserver::create( |
| const AtomicString& id, |
| HTMLInputElement* element) { |
| return new ListAttributeTargetObserver(id, element); |
| } |
| |
| ListAttributeTargetObserver::ListAttributeTargetObserver( |
| const AtomicString& id, |
| HTMLInputElement* element) |
| : IdTargetObserver(element->treeScope().idTargetObserverRegistry(), id), |
| m_element(element) {} |
| |
| DEFINE_TRACE(ListAttributeTargetObserver) { |
| visitor->trace(m_element); |
| IdTargetObserver::trace(visitor); |
| } |
| |
| void ListAttributeTargetObserver::idTargetChanged() { |
| m_element->listAttributeTargetChanged(); |
| } |
| |
| void HTMLInputElement::setRangeText(const String& replacement, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| |
| HTMLTextFormControlElement::setRangeText(replacement, exceptionState); |
| } |
| |
| void HTMLInputElement::setRangeText(const String& replacement, |
| unsigned start, |
| unsigned end, |
| const String& selectionMode, |
| ExceptionState& exceptionState) { |
| if (!m_inputType->supportsSelectionAPI()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "The input element's type ('" + |
| m_inputType->formControlType() + |
| "') does not support selection."); |
| return; |
| } |
| |
| HTMLTextFormControlElement::setRangeText(replacement, start, end, |
| selectionMode, exceptionState); |
| } |
| |
| bool HTMLInputElement::setupDateTimeChooserParameters( |
| DateTimeChooserParameters& parameters) { |
| if (!document().view()) |
| return false; |
| |
| parameters.type = type(); |
| parameters.minimum = minimum(); |
| parameters.maximum = maximum(); |
| parameters.required = isRequired(); |
| if (!RuntimeEnabledFeatures::langAttributeAwareFormControlUIEnabled()) { |
| parameters.locale = defaultLanguage(); |
| } else { |
| AtomicString computedLocale = computeInheritedLanguage(); |
| parameters.locale = |
| computedLocale.isEmpty() ? defaultLanguage() : computedLocale; |
| } |
| |
| StepRange stepRange = createStepRange(RejectAny); |
| if (stepRange.hasStep()) { |
| parameters.step = stepRange.step().toDouble(); |
| parameters.stepBase = stepRange.stepBase().toDouble(); |
| } else { |
| parameters.step = 1.0; |
| parameters.stepBase = 0; |
| } |
| |
| parameters.anchorRectInScreen = |
| document().view()->contentsToScreen(pixelSnappedBoundingBox()); |
| parameters.currentValue = value(); |
| parameters.doubleValue = m_inputType->valueAsDouble(); |
| parameters.isAnchorElementRTL = |
| m_inputTypeView->computedTextDirection() == RTL; |
| if (HTMLDataListElement* dataList = this->dataList()) { |
| HTMLDataListOptionsCollection* options = dataList->options(); |
| for (unsigned i = 0; HTMLOptionElement* option = options->item(i); ++i) { |
| if (!isValidValue(option->value())) |
| continue; |
| DateTimeSuggestion suggestion; |
| suggestion.value = |
| m_inputType->parseToNumber(option->value(), Decimal::nan()) |
| .toDouble(); |
| if (std::isnan(suggestion.value)) |
| continue; |
| suggestion.localizedValue = localizeValue(option->value()); |
| suggestion.label = |
| option->value() == option->label() ? String() : option->label(); |
| parameters.suggestions.append(suggestion); |
| } |
| } |
| return true; |
| } |
| |
| bool HTMLInputElement::supportsInputModeAttribute() const { |
| return m_inputType->supportsInputModeAttribute(); |
| } |
| |
| void HTMLInputElement::setShouldRevealPassword(bool value) { |
| if (!!m_shouldRevealPassword == value) |
| return; |
| m_shouldRevealPassword = value; |
| lazyReattachIfAttached(); |
| } |
| |
| bool HTMLInputElement::isInteractiveContent() const { |
| return m_inputType->isInteractiveContent(); |
| } |
| |
| bool HTMLInputElement::supportsAutofocus() const { |
| return m_inputType->isInteractiveContent(); |
| } |
| |
| PassRefPtr<ComputedStyle> HTMLInputElement::customStyleForLayoutObject() { |
| return m_inputTypeView->customStyleForLayoutObject( |
| originalStyleForLayoutObject()); |
| } |
| |
| bool HTMLInputElement::shouldDispatchFormControlChangeEvent(String& oldValue, |
| String& newValue) { |
| return m_inputType->shouldDispatchFormControlChangeEvent(oldValue, newValue); |
| } |
| |
| void HTMLInputElement::didNotifySubtreeInsertionsToDocument() { |
| listAttributeTargetChanged(); |
| } |
| |
| AXObject* HTMLInputElement::popupRootAXObject() { |
| return m_inputTypeView->popupRootAXObject(); |
| } |
| |
| void HTMLInputElement::ensureFallbackContent() { |
| m_inputTypeView->ensureFallbackContent(); |
| } |
| |
| void HTMLInputElement::ensurePrimaryContent() { |
| m_inputTypeView->ensurePrimaryContent(); |
| } |
| |
| bool HTMLInputElement::hasFallbackContent() const { |
| return m_inputTypeView->hasFallbackContent(); |
| } |
| |
| void HTMLInputElement::setFilesFromPaths(const Vector<String>& paths) { |
| return m_inputType->setFilesFromPaths(paths); |
| } |
| |
| } // namespace blink |