diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/html/HTMLInputElement.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/html/HTMLInputElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLInputElement.cpp | 962 |
1 files changed, 534 insertions, 428 deletions
diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp index ed1c20ad5..ec557d0b1 100644 --- a/Source/WebCore/html/HTMLInputElement.cpp +++ b/Source/WebCore/html/HTMLInputElement.cpp @@ -2,7 +2,7 @@ * 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, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2004-2017 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. @@ -49,7 +49,6 @@ #include "HTMLOptionElement.h" #include "HTMLParserIdioms.h" #include "IdTargetObserver.h" -#include "InsertionPoint.h" #include "KeyboardEvent.h" #include "Language.h" #include "LocalizedStrings.h" @@ -57,22 +56,13 @@ #include "PlatformMouseEvent.h" #include "RenderTextControlSingleLine.h" #include "RenderTheme.h" -#include "RuntimeEnabledFeatures.h" #include "ScopedEventQueue.h" #include "SearchInputType.h" +#include "Settings.h" #include "StyleResolver.h" -#include "TextBreakIterator.h" #include <wtf/MathExtras.h> #include <wtf/Ref.h> -#if ENABLE(INPUT_TYPE_COLOR) -#include "ColorInputType.h" -#endif - -#if ENABLE(INPUT_SPEECH) -#include "RuntimeEnabledFeatures.h" -#endif - #if ENABLE(TOUCH_EVENTS) #include "TouchEvent.h" #endif @@ -85,12 +75,11 @@ using namespace HTMLNames; class ListAttributeTargetObserver : IdTargetObserver { WTF_MAKE_FAST_ALLOCATED; public: - static OwnPtr<ListAttributeTargetObserver> create(const AtomicString& id, HTMLInputElement*); - virtual void idTargetChanged() override; - -private: ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement*); + void idTargetChanged() override; + +private: HTMLInputElement* m_element; }; #endif @@ -99,14 +88,13 @@ private: // 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 unsigned HTMLInputElement::maxEffectiveLength = 524288; const int defaultSize = 20; const int maxSavedResults = 256; HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) : HTMLTextFormControlElement(tagName, document, form) , m_size(defaultSize) - , m_maxLength(maximumLength) , m_maxResults(-1) , m_isChecked(false) , m_reflectsCheckedAttribute(true) @@ -114,7 +102,8 @@ HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document& docum , m_hasType(false) , m_isActivatedSubmit(false) , m_autocomplete(Uninitialized) - , m_isAutofilled(false) + , m_isAutoFilled(false) + , m_autoFillButtonType(static_cast<uint8_t>(AutoFillButtonType::None)) #if ENABLE(DATALIST_ELEMENT) , m_hasNonEmptyList(false) #endif @@ -126,35 +115,41 @@ HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document& docum #if ENABLE(TOUCH_EVENTS) , m_hasTouchEventHandler(false) #endif - , m_inputType(InputType::createText(*this)) + , m_isSpellcheckDisabledExceptTextReplacement(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)) { - ASSERT(hasTagName(inputTag) || hasTagName(isindexTag)); + ASSERT(hasTagName(inputTag)); setHasCustomStyleResolveCallbacks(); } -PassRefPtr<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) +Ref<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) { - RefPtr<HTMLInputElement> inputElement = adoptRef(new HTMLInputElement(tagName, document, form, createdByParser)); - inputElement->ensureUserAgentShadowRoot(); - return inputElement.release(); + bool shouldCreateShadowRootLazily = createdByParser; + Ref<HTMLInputElement> inputElement = adoptRef(*new HTMLInputElement(tagName, document, form, createdByParser)); + if (!shouldCreateShadowRootLazily) + inputElement->ensureUserAgentShadowRoot(); + return inputElement; } -HTMLImageLoader* HTMLInputElement::imageLoader() +HTMLImageLoader& HTMLInputElement::ensureImageLoader() { if (!m_imageLoader) - m_imageLoader = adoptPtr(new HTMLImageLoader(this)); - return m_imageLoader.get(); + m_imageLoader = std::make_unique<HTMLImageLoader>(*this); + return *m_imageLoader; } void HTMLInputElement::didAddUserAgentShadowRoot(ShadowRoot*) { m_inputType->createShadowSubtree(); + updateInnerTextElementEditability(); } HTMLInputElement::~HTMLInputElement() { if (needsSuspensionCallback()) - document().unregisterForPageCacheSuspensionCallbacks(this); + document().unregisterForDocumentSuspensionCallbacks(this); // Need to remove form association while this is still an HTMLInputElement // so that virtual functions are called correctly. @@ -162,10 +157,10 @@ HTMLInputElement::~HTMLInputElement() // setForm(0) may register this to a document-level radio button group. // We should unregister it to avoid accessing a deleted object. if (isRadioButton()) - document().formController().checkedRadioButtons().removeButton(this); + document().formController().radioButtonGroups().removeButton(this); #if ENABLE(TOUCH_EVENTS) if (m_hasTouchEventHandler) - document().didRemoveEventTargetNode(this); + document().didRemoveEventTargetNode(*this); #endif } @@ -199,6 +194,16 @@ HTMLElement* HTMLInputElement::innerSpinButtonElement() const return m_inputType->innerSpinButtonElement(); } +HTMLElement* HTMLInputElement::capsLockIndicatorElement() const +{ + return m_inputType->capsLockIndicatorElement(); +} + +HTMLElement* HTMLInputElement::autoFillButtonElement() const +{ + return m_inputType->autoFillButtonElement(); +} + HTMLElement* HTMLInputElement::resultsButtonElement() const { return m_inputType->resultsButtonElement(); @@ -209,13 +214,6 @@ HTMLElement* HTMLInputElement::cancelButtonElement() const return m_inputType->cancelButtonElement(); } -#if ENABLE(INPUT_SPEECH) -HTMLElement* HTMLInputElement::speechButtonElement() const -{ - return m_inputType->speechButtonElement(); -} -#endif - HTMLElement* HTMLInputElement::sliderThumbElement() const { return m_inputType->sliderThumbElement(); @@ -248,11 +246,17 @@ bool HTMLInputElement::isValidValue(const String& value) const && !m_inputType->stepMismatch(value) && !m_inputType->rangeUnderflow(value) && !m_inputType->rangeOverflow(value) + && !tooShort(value, IgnoreDirtyFlag) && !tooLong(value, IgnoreDirtyFlag) && !m_inputType->patternMismatch(value) && !m_inputType->valueMissing(value); } +bool HTMLInputElement::tooShort() const +{ + return willValidate() && tooShort(value(), CheckDirtyFlag); +} + bool HTMLInputElement::tooLong() const { return willValidate() && tooLong(value(), CheckDirtyFlag); @@ -278,22 +282,43 @@ bool HTMLInputElement::patternMismatch() const return willValidate() && m_inputType->patternMismatch(value()); } -bool HTMLInputElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const +bool HTMLInputElement::tooShort(StringView value, NeedsToCheckDirtyFlag check) const { - // We use isTextType() instead of supportsMaxLength() because of the - // 'virtual' overhead. - if (!isTextType()) + if (!supportsMinLength()) return false; - int max = maxLength(); - if (max < 0) + + int min = minLength(); + if (min <= 0) return false; + + if (check == CheckDirtyFlag) { + // Return false for the default value or a value set by a script even if + // it is shorter than minLength. + if (!hasDirtyValue() || !m_wasModifiedByUser) + return false; + } + + // The empty string is excluded from tooShort validation. + if (value.isEmpty()) + return false; + + // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length. + return numGraphemeClusters(value) < static_cast<unsigned>(min); +} + +bool HTMLInputElement::tooLong(StringView value, NeedsToCheckDirtyFlag check) const +{ + if (!supportsMaxLength()) + return false; + unsigned max = effectiveMaxLength(); if (check == CheckDirtyFlag) { // Return false for the default value or a value set by a script even if // it is longer than maxLength. if (!hasDirtyValue() || !m_wasModifiedByUser) return false; } - return numGraphemeClusters(value) > static_cast<unsigned>(max); + // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length. + return numGraphemeClusters(value) > max; } bool HTMLInputElement::rangeUnderflow() const @@ -332,6 +357,18 @@ bool HTMLInputElement::stepMismatch() const return willValidate() && m_inputType->stepMismatch(value()); } +bool HTMLInputElement::isValid() const +{ + if (!willValidate()) + return true; + + String value = this->value(); + bool someError = m_inputType->typeMismatch() || m_inputType->stepMismatch(value) || m_inputType->rangeUnderflow(value) || m_inputType->rangeOverflow(value) + || tooShort(value, CheckDirtyFlag) || tooLong(value, CheckDirtyFlag) || m_inputType->patternMismatch(value) || m_inputType->valueMissing(value) + || m_inputType->hasBadInput() || customError(); + return !someError; +} + bool HTMLInputElement::getAllowedValueStep(Decimal* step) const { return m_inputType->getAllowedValueStep(step); @@ -343,20 +380,20 @@ StepRange HTMLInputElement::createStepRange(AnyStepHandling anyStepHandling) con } #if ENABLE(DATALIST_ELEMENT) -Decimal HTMLInputElement::findClosestTickMarkValue(const Decimal& value) +std::optional<Decimal> HTMLInputElement::findClosestTickMarkValue(const Decimal& value) { return m_inputType->findClosestTickMarkValue(value); } #endif -void HTMLInputElement::stepUp(int n, ExceptionCode& ec) +ExceptionOr<void> HTMLInputElement::stepUp(int n) { - m_inputType->stepUp(n, ec); + return m_inputType->stepUp(n); } -void HTMLInputElement::stepDown(int n, ExceptionCode& ec) +ExceptionOr<void> HTMLInputElement::stepDown(int n) { - m_inputType->stepUp(-n, ec); + return m_inputType->stepUp(-n); } void HTMLInputElement::blur() @@ -374,7 +411,7 @@ bool HTMLInputElement::hasCustomFocusLogic() const return m_inputType->hasCustomFocusLogic(); } -bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const +bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent& event) const { return m_inputType->isKeyboardFocusable(event); } @@ -389,7 +426,7 @@ bool HTMLInputElement::isTextFormControlFocusable() const return HTMLTextFormControlElement::isFocusable(); } -bool HTMLInputElement::isTextFormControlKeyboardFocusable(KeyboardEvent* event) const +bool HTMLInputElement::isTextFormControlKeyboardFocusable(KeyboardEvent& event) const { return HTMLTextFormControlElement::isKeyboardFocusable(event); } @@ -399,17 +436,17 @@ bool HTMLInputElement::isTextFormControlMouseFocusable() const return HTMLTextFormControlElement::isMouseFocusable(); } -void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection) +void HTMLInputElement::updateFocusAppearance(SelectionRestorationMode restorationMode, SelectionRevealMode revealMode) { if (isTextField()) { - if (!restorePreviousSelection || !hasCachedSelection()) - select(); + if (restorationMode == SelectionRestorationMode::SetDefault || !hasCachedSelection()) + select(Element::defaultFocusTextStateChangeIntent()); else restoreCachedSelection(); if (document().frame()) - document().frame()->selection().revealSelection(); + document().frame()->selection().revealSelection(revealMode); } else - HTMLTextFormControlElement::updateFocusAppearance(restorePreviousSelection); + HTMLTextFormControlElement::updateFocusAppearance(restorationMode, revealMode); } void HTMLInputElement::endEditing() @@ -436,65 +473,42 @@ void HTMLInputElement::handleBlurEvent() m_inputType->handleBlurEvent(); } -void HTMLInputElement::setType(const String& type) +void HTMLInputElement::setType(const AtomicString& type) { - // FIXME: This should just call setAttribute. No reason to handle the empty string specially. - // We should write a test case to show that setting to the empty string does not remove the - // attribute in other browsers and then fix this. Note that setting to null *does* remove - // the attribute and setAttribute implements that. - if (type.isEmpty()) - removeAttribute(typeAttr); - else - setAttribute(typeAttr, type); + setAttributeWithoutSynchronization(typeAttr, type); } void HTMLInputElement::updateType() { - auto newType = InputType::create(*this, fastGetAttribute(typeAttr)); - bool hadType = m_hasType; + ASSERT(m_inputType); + auto newType = InputType::create(*this, attributeWithoutSynchronization(typeAttr)); m_hasType = true; if (m_inputType->formControlType() == newType->formControlType()) return; - if (hadType && !newType->canChangeFromAnotherType()) { - // Set the attribute back to the old value. - // Useful in case we were called from inside parseAttribute. - setAttribute(typeAttr, type()); - return; - } - removeFromRadioButtonGroup(); bool didStoreValue = m_inputType->storesValueSeparateFromAttribute(); bool neededSuspensionCallback = needsSuspensionCallback(); bool didRespectHeightAndWidth = m_inputType->shouldRespectHeightAndWidthAttributes(); + bool wasSuccessfulSubmitButtonCandidate = m_inputType->canBeSuccessfulSubmitButton(); m_inputType->destroyShadowSubtree(); - m_inputType = std::move(newType); + m_inputType = WTFMove(newType); m_inputType->createShadowSubtree(); - -#if ENABLE(TOUCH_EVENTS) - bool hasTouchEventHandler = m_inputType->hasTouchEventHandler(); - if (hasTouchEventHandler != m_hasTouchEventHandler) { - if (hasTouchEventHandler) - document().didAddTouchEventHandler(this); - else - document().didRemoveTouchEventHandler(this); - m_hasTouchEventHandler = hasTouchEventHandler; - } -#endif + updateInnerTextElementEditability(); setNeedsWillValidateCheck(); bool willStoreValue = m_inputType->storesValueSeparateFromAttribute(); if (didStoreValue && !willStoreValue && hasDirtyValue()) { - setAttribute(valueAttr, m_valueIfDirty); + setAttributeWithoutSynchronization(valueAttr, m_valueIfDirty); m_valueIfDirty = String(); } if (!didStoreValue && willStoreValue) { - AtomicString valueString = fastGetAttribute(valueAttr); + AtomicString valueString = attributeWithoutSynchronization(valueAttr); m_valueIfDirty = sanitizeValue(valueString); } else updateValueIfNeeded(); @@ -520,21 +534,37 @@ void HTMLInputElement::updateType() attributeChanged(alignAttr, nullAtom, align->value()); } + if (form() && wasSuccessfulSubmitButtonCandidate != m_inputType->canBeSuccessfulSubmitButton()) + form()->resetDefaultButton(); + + runPostTypeUpdateTasks(); +} + +inline void HTMLInputElement::runPostTypeUpdateTasks() +{ + ASSERT(m_inputType); +#if ENABLE(TOUCH_EVENTS) + bool hasTouchEventHandler = m_inputType->hasTouchEventHandler(); + if (hasTouchEventHandler != m_hasTouchEventHandler) { + if (hasTouchEventHandler) + document().didAddTouchEventHandler(*this); + else + document().didRemoveTouchEventHandler(*this); + m_hasTouchEventHandler = hasTouchEventHandler; + } +#endif + if (renderer()) - setNeedsStyleRecalc(ReconstructRenderTree); + invalidateStyleAndRenderersForSubtree(); if (document().focusedElement() == this) - updateFocusAppearance(true); - - if (ShadowRoot* shadowRoot = shadowRootOfParentForDistribution(this)) - shadowRoot->invalidateDistribution(); + updateFocusAppearance(SelectionRestorationMode::Restore, SelectionRevealMode::Reveal); setChangedSinceLastFormControlChangeEvent(false); addToRadioButtonGroup(); - setNeedsValidityCheck(); - notifyFormStateChanged(); + updateValidity(); } void HTMLInputElement::subtreeHasChanged() @@ -614,15 +644,38 @@ void HTMLInputElement::collectStyleForPresentationAttribute(const QualifiedName& HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style); } +inline void HTMLInputElement::initializeInputType() +{ + ASSERT(m_parsingInProgress); + ASSERT(!m_inputType); + + const AtomicString& type = attributeWithoutSynchronization(typeAttr); + if (type.isNull()) { + m_inputType = InputType::createText(*this); + ensureUserAgentShadowRoot(); + setNeedsWillValidateCheck(); + return; + } + + m_hasType = true; + m_inputType = InputType::create(*this, type); + ensureUserAgentShadowRoot(); + setNeedsWillValidateCheck(); + registerForSuspensionCallbackIfNeeded(); + runPostTypeUpdateTasks(); +} + void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { + ASSERT(m_inputType); + if (name == nameAttr) { removeFromRadioButtonGroup(); m_name = value; addToRadioButtonGroup(); HTMLTextFormControlElement::parseAttribute(name, value); } else if (name == autocompleteAttr) { - if (equalIgnoringCase(value, "off")) { + if (equalLettersIgnoringASCIICase(value, "off")) { m_autocomplete = Off; registerForSuspensionCallbackIfNeeded(); } else { @@ -647,14 +700,16 @@ void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicStr } // We only need to setChanged if the form is looking at the default value right now. if (!hasDirtyValue()) { - updatePlaceholderVisibility(false); - setNeedsStyleRecalc(); + updatePlaceholderVisibility(); + invalidateStyleForSubtree(); } setFormControlValueMatchesRenderer(false); - setNeedsValidityCheck(); + updateValidity(); m_valueAttributeWasUpdatedAfterParsing = !m_parsingInProgress; - m_inputType->valueAttributeChanged(); } else if (name == checkedAttr) { + if (m_inputType->isCheckable()) + invalidateStyleForSubtree(); + // 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 @@ -664,11 +719,12 @@ void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicStr m_reflectsCheckedAttribute = true; } } else if (name == maxlengthAttr) - parseMaxLengthAttribute(value); + maxLengthAttributeChanged(value); + else if (name == minlengthAttr) + minLengthAttributeChanged(value); else if (name == sizeAttr) { - int oldSize = m_size; - int valueAsInteger = value.toInt(); - m_size = valueAsInteger > 0 ? valueAsInteger : defaultSize; + unsigned oldSize = m_size; + m_size = limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(value, defaultSize); if (m_size != oldSize && renderer()) renderer()->setNeedsLayoutAndPrefWidthsRecalc(); } else if (name == altAttr) @@ -677,45 +733,29 @@ void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicStr m_inputType->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(eventNames().searchEvent, name, value); } else if (name == resultsAttr) { - int oldResults = m_maxResults; m_maxResults = !value.isNull() ? std::min(value.toInt(), maxSavedResults) : -1; - - if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0)) - setNeedsStyleRecalc(ReconstructRenderTree); - else - setNeedsStyleRecalc(); - FeatureObserver::observe(&document(), FeatureObserver::ResultsAttribute); + m_inputType->maxResultsAttributeChanged(); } else if (name == autosaveAttr) { - setNeedsStyleRecalc(); - FeatureObserver::observe(&document(), FeatureObserver::AutoSaveAttribute); + invalidateStyleForSubtree(); } else if (name == incrementalAttr) { - setNeedsStyleRecalc(); - FeatureObserver::observe(&document(), FeatureObserver::IncrementalAttribute); + invalidateStyleForSubtree(); } else if (name == minAttr) { m_inputType->minOrMaxAttributeChanged(); - setNeedsValidityCheck(); - FeatureObserver::observe(&document(), FeatureObserver::MinAttribute); + updateValidity(); } else if (name == maxAttr) { m_inputType->minOrMaxAttributeChanged(); - setNeedsValidityCheck(); - FeatureObserver::observe(&document(), FeatureObserver::MaxAttribute); + updateValidity(); } else if (name == multipleAttr) { m_inputType->multipleAttributeChanged(); - setNeedsValidityCheck(); + updateValidity(); } else if (name == stepAttr) { m_inputType->stepAttributeChanged(); - setNeedsValidityCheck(); - FeatureObserver::observe(&document(), FeatureObserver::StepAttribute); + updateValidity(); } else if (name == patternAttr) { - setNeedsValidityCheck(); - FeatureObserver::observe(&document(), FeatureObserver::PatternAttribute); + updateValidity(); } else if (name == precisionAttr) { - setNeedsValidityCheck(); - FeatureObserver::observe(&document(), FeatureObserver::PrecisionAttribute); + updateValidity(); } else if (name == disabledAttr) { HTMLTextFormControlElement::parseAttribute(name, value); m_inputType->disabledAttributeChanged(); @@ -730,41 +770,27 @@ void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicStr resetListAttributeTargetObserver(); listAttributeTargetChanged(); } - FeatureObserver::observe(&document(), FeatureObserver::ListAttribute); - } -#endif -#if ENABLE(INPUT_SPEECH) - else if (name == webkitspeechAttr) { - m_inputType->destroyShadowSubtree(); - m_inputType->createShadowSubtree(); - - // This renderer and its children have quite different layouts and styles depending on - // whether the speech button is visible or not. So we reset the whole thing and recreate - // to get the right styles and layout. - setNeedsStyleRecalc(ReconstructRenderTree); - - setFormControlValueMatchesRenderer(false); - FeatureObserver::observe(&document(), FeatureObserver::PrefixedSpeechAttribute); - } else if (name == onwebkitspeechchangeAttr) - setAttributeEventListener(eventNames().webkitspeechchangeEvent, name, value); -#endif -#if ENABLE(DIRECTORY_UPLOAD) - else if (name == webkitdirectoryAttr) { - HTMLTextFormControlElement::parseAttribute(name, value); - FeatureObserver::observe(&document(), FeatureObserver::PrefixedDirectoryAttribute); } #endif else HTMLTextFormControlElement::parseAttribute(name, value); - m_inputType->attributeChanged(); + + m_inputType->attributeChanged(name); +} + +void HTMLInputElement::parserDidSetAttributes() +{ + ASSERT(m_parsingInProgress); + initializeInputType(); } void HTMLInputElement::finishParsingChildren() { m_parsingInProgress = false; + ASSERT(m_inputType); HTMLTextFormControlElement::finishParsingChildren(); if (!m_stateRestored) { - bool checked = hasAttribute(checkedAttr); + bool checked = hasAttributeWithoutSynchronization(checkedAttr); if (checked) setChecked(checked); m_reflectsCheckedAttribute = true; @@ -776,9 +802,9 @@ bool HTMLInputElement::rendererIsNeeded(const RenderStyle& style) return m_inputType->rendererIsNeeded() && HTMLTextFormControlElement::rendererIsNeeded(style); } -RenderPtr<RenderElement> HTMLInputElement::createElementRenderer(PassRef<RenderStyle> style) +RenderPtr<RenderElement> HTMLInputElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) { - return m_inputType->createInputRenderer(std::move(style)); + return m_inputType->createInputRenderer(WTFMove(style)); } void HTMLInputElement::willAttachRenderers() @@ -794,7 +820,7 @@ void HTMLInputElement::didAttachRenderers() m_inputType->attach(); if (document().focusedElement() == this) - document().updateFocusAppearanceSoon(true /* restore selection */); + document().updateFocusAppearanceSoon(SelectionRestorationMode::Restore); } void HTMLInputElement::didDetachRenderers() @@ -808,12 +834,12 @@ 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); + String alt = attributeWithoutSynchronization(altAttr); // fall back to title attribute if (alt.isNull()) - alt = getAttribute(titleAttr); + alt = attributeWithoutSynchronization(titleAttr); if (alt.isNull()) - alt = getAttribute(valueAttr); + alt = attributeWithoutSynchronization(valueAttr); if (alt.isEmpty()) alt = inputElementAltText(); return alt; @@ -826,6 +852,14 @@ bool HTMLInputElement::isSuccessfulSubmitButton() const return !isDisabledFormControl() && m_inputType->canBeSuccessfulSubmitButton(); } +bool HTMLInputElement::matchesDefaultPseudoClass() const +{ + ASSERT(m_inputType); + if (m_inputType->canBeSuccessfulSubmitButton()) + return !isDisabledFormControl() && form() && form()->defaultButton() == this; + return m_inputType->isCheckable() && hasAttributeWithoutSynchronization(checkedAttr); +} + bool HTMLInputElement::isActivatedSubmit() const { return m_isActivatedSubmit; @@ -846,8 +880,8 @@ void HTMLInputElement::reset() if (m_inputType->storesValueSeparateFromAttribute()) setValue(String()); - setAutofilled(false); - setChecked(hasAttribute(checkedAttr)); + setAutoFilled(false); + setChecked(hasAttributeWithoutSynchronization(checkedAttr)); m_reflectsCheckedAttribute = true; } @@ -868,13 +902,13 @@ void HTMLInputElement::setChecked(bool nowChecked, TextFieldEventBehavior eventB m_reflectsCheckedAttribute = false; m_isChecked = nowChecked; - setNeedsStyleRecalc(); + invalidateStyleForSubtree(); - if (CheckedRadioButtons* buttons = checkedRadioButtons()) - buttons->updateCheckedState(this); + if (RadioButtonGroups* buttons = radioButtonGroups()) + buttons->updateCheckedState(this); if (renderer() && renderer()->style().hasAppearance()) - renderer()->theme().stateChanged(renderer(), CheckedState); - setNeedsValidityCheck(); + renderer()->theme().stateChanged(*renderer(), ControlStates::CheckedState); + updateValidity(); // Ideally we'd do this from the render tree (matching // RenderTextView), but it's not possible to do it at the moment @@ -889,12 +923,12 @@ void HTMLInputElement::setChecked(bool nowChecked, TextFieldEventBehavior eventB // 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 && inDocument() && m_inputType->shouldSendChangeEventAfterCheckedChanged()) { + if (eventBehavior != DispatchNoEvent && isConnected() && m_inputType->shouldSendChangeEventAfterCheckedChanged()) { setTextAsOfLastFormControlChangeEvent(String()); dispatchFormControlChangeEvent(); } - didAffectSelector(AffectedSelectorChecked); + invalidateStyleForSubtree(); } void HTMLInputElement::setIndeterminate(bool newValue) @@ -904,13 +938,13 @@ void HTMLInputElement::setIndeterminate(bool newValue) m_isIndeterminate = newValue; - didAffectSelector(AffectedSelectorIndeterminate); + invalidateStyleForSubtree(); if (renderer() && renderer()->style().hasAppearance()) - renderer()->theme().stateChanged(renderer(), CheckedState); + renderer()->theme().stateChanged(*renderer(), ControlStates::CheckedState); } -int HTMLInputElement::size() const +unsigned HTMLInputElement::size() const { return m_size; } @@ -927,7 +961,7 @@ float HTMLInputElement::decorationWidth() const void HTMLInputElement::copyNonAttributePropertiesFromElement(const Element& source) { - const HTMLInputElement& sourceElement = static_cast<const HTMLInputElement&>(source); + auto& sourceElement = downcast<HTMLInputElement>(source); m_valueIfDirty = sourceElement.m_valueIfDirty; m_wasModifiedByUser = false; @@ -937,6 +971,7 @@ void HTMLInputElement::copyNonAttributePropertiesFromElement(const Element& sour HTMLTextFormControlElement::copyNonAttributePropertiesFromElement(source); + updateValidity(); setFormControlValueMatchesRenderer(false); m_inputType->updateInnerTextValue(); } @@ -951,7 +986,7 @@ String HTMLInputElement::value() const if (!value.isNull()) return value; - AtomicString valueString = fastGetAttribute(valueAttr); + auto& valueString = attributeWithoutSynchronization(valueAttr); value = sanitizeValue(valueString); if (!value.isNull()) return value; @@ -974,21 +1009,6 @@ void HTMLInputElement::setValueForUser(const String& value) setValue(value, DispatchChangeEvent); } -const String& HTMLInputElement::suggestedValue() const -{ - return m_suggestedValue; -} - -void HTMLInputElement::setSuggestedValue(const String& value) -{ - if (!m_inputType->canSetSuggestedValue()) - return; - setFormControlValueMatchesRenderer(false); - m_suggestedValue = sanitizeValue(value); - setNeedsStyleRecalc(); - m_inputType->updateInnerTextValue(); -} - void HTMLInputElement::setEditingValue(const String& value) { if (!renderer() || !isTextField()) @@ -1005,41 +1025,30 @@ void HTMLInputElement::setEditingValue(const String& value) dispatchInputEvent(); } -void HTMLInputElement::setValue(const String& value, ExceptionCode& ec, TextFieldEventBehavior eventBehavior) +ExceptionOr<void> HTMLInputElement::setValue(const String& value, TextFieldEventBehavior eventBehavior) { - if (isFileUpload() && !value.isEmpty()) { - ec = INVALID_STATE_ERR; - return; - } - setValue(value, eventBehavior); -} + if (isFileUpload() && !value.isEmpty()) + return Exception { INVALID_STATE_ERR }; -void HTMLInputElement::setValue(const String& value, TextFieldEventBehavior eventBehavior) -{ if (!m_inputType->canSetValue(value)) - return; + return { }; - Ref<HTMLInputElement> protect(*this); + Ref<HTMLInputElement> protectedThis(*this); EventQueueScope scope; String sanitizedValue = sanitizeValue(value); bool valueChanged = sanitizedValue != this->value(); setLastChangeWasNotUserEdit(); setFormControlValueMatchesRenderer(false); - m_suggestedValue = String(); // Prevent TextFieldInputType::setValue from using the suggested value. m_inputType->setValue(sanitizedValue, valueChanged, eventBehavior); - - if (!valueChanged) - return; - - notifyFormStateChanged(); + return { }; } void HTMLInputElement::setValueInternal(const String& sanitizedValue, TextFieldEventBehavior eventBehavior) { m_valueIfDirty = sanitizedValue; m_wasModifiedByUser = eventBehavior != DispatchNoEvent; - setNeedsValidityCheck(); + updateValidity(); } double HTMLInputElement::valueAsDate() const @@ -1047,9 +1056,9 @@ double HTMLInputElement::valueAsDate() const return m_inputType->valueAsDate(); } -void HTMLInputElement::setValueAsDate(double value, ExceptionCode& ec) +ExceptionOr<void> HTMLInputElement::setValueAsDate(double value) { - m_inputType->setValueAsDate(value, ec); + return m_inputType->setValueAsDate(value); } double HTMLInputElement::valueAsNumber() const @@ -1057,13 +1066,11 @@ double HTMLInputElement::valueAsNumber() const return m_inputType->valueAsDouble(); } -void HTMLInputElement::setValueAsNumber(double newValue, ExceptionCode& ec, TextFieldEventBehavior eventBehavior) +ExceptionOr<void> HTMLInputElement::setValueAsNumber(double newValue, TextFieldEventBehavior eventBehavior) { - if (!std::isfinite(newValue)) { - ec = NOT_SUPPORTED_ERR; - return; - } - m_inputType->setValueAsDouble(newValue, eventBehavior, ec); + if (!std::isfinite(newValue)) + return Exception { NOT_SUPPORTED_ERR }; + return m_inputType->setValueAsDouble(newValue, eventBehavior); } void HTMLInputElement::setValueFromRenderer(const String& value) @@ -1071,13 +1078,15 @@ void HTMLInputElement::setValueFromRenderer(const String& value) // File upload controls will never use this. ASSERT(!isFileUpload()); - m_suggestedValue = String(); - // Renderer and our event handler are responsible for sanitizing values. - ASSERT(value == sanitizeValue(value) || sanitizeValue(value).isEmpty()); + // Input types that support the selection API do *not* sanitize their + // user input in order to retain parity between what's in the model and + // what's on the screen. + ASSERT(m_inputType->supportsSelectionAPI() || value == sanitizeValue(value) || sanitizeValue(value).isEmpty()); // Workaround for bug where trailing \n is included in the result of textContent. - // The assert macro above may also be simplified to: value == constrainValue(value) + // The assert macro above may also be simplified by removing the expression + // that calls isEmpty. // http://bugs.webkit.org/show_bug.cgi?id=9661 m_valueIfDirty = value == "\n" ? emptyString() : value; @@ -1087,19 +1096,18 @@ void HTMLInputElement::setValueFromRenderer(const String& value) // Input event is fired by the Node::defaultEventHandler for editable controls. if (!isTextField()) dispatchInputEvent(); - notifyFormStateChanged(); - setNeedsValidityCheck(); + updateValidity(); - // Clear autofill flag (and yellow background) on user edit. - setAutofilled(false); + // Clear auto fill flag (and yellow background) on user edit. + setAutoFilled(false); } void HTMLInputElement::willDispatchEvent(Event& event, InputElementClickState& state) { - if (event.type() == eventNames().textInputEvent && m_inputType->shouldSubmitImplicitly(&event)) + if (event.type() == eventNames().textInputEvent && m_inputType->shouldSubmitImplicitly(event)) event.stopPropagation(); - if (event.type() == eventNames().clickEvent && event.isMouseEvent() && toMouseEvent(&event)->button() == LeftButton) { + if (event.type() == eventNames().clickEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { m_inputType->willDispatchClick(state); state.stateful = true; } @@ -1110,34 +1118,34 @@ void HTMLInputElement::didDispatchClickEvent(Event& event, const InputElementCli m_inputType->didDispatchClick(&event, state); } -void HTMLInputElement::defaultEventHandler(Event* evt) +void HTMLInputElement::defaultEventHandler(Event& event) { - if (evt->isMouseEvent() && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { - m_inputType->handleClickEvent(static_cast<MouseEvent*>(evt)); - if (evt->defaultHandled()) + if (is<MouseEvent>(event) && event.type() == eventNames().clickEvent && downcast<MouseEvent>(event).button() == LeftButton) { + m_inputType->handleClickEvent(downcast<MouseEvent>(event)); + if (event.defaultHandled()) return; } #if ENABLE(TOUCH_EVENTS) - if (evt->isTouchEvent()) { - m_inputType->handleTouchEvent(static_cast<TouchEvent*>(evt)); - if (evt->defaultHandled()) + if (is<TouchEvent>(event)) { + m_inputType->handleTouchEvent(downcast<TouchEvent>(event)); + if (event.defaultHandled()) return; } #endif - if (evt->isKeyboardEvent() && evt->type() == eventNames().keydownEvent) { - m_inputType->handleKeydownEvent(static_cast<KeyboardEvent*>(evt)); - if (evt->defaultHandled()) + if (is<KeyboardEvent>(event) && event.type() == eventNames().keydownEvent) { + m_inputType->handleKeydownEvent(downcast<KeyboardEvent>(event)); + if (event.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() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent); + bool callBaseClassEarly = isTextField() && (event.type() == eventNames().keydownEvent || event.type() == eventNames().keypressEvent); if (callBaseClassEarly) { - HTMLTextFormControlElement::defaultEventHandler(evt); - if (evt->defaultHandled()) + HTMLTextFormControlElement::defaultEventHandler(event); + if (event.defaultHandled()) return; } @@ -1145,27 +1153,28 @@ void HTMLInputElement::defaultEventHandler(Event* evt) // 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() == eventNames().DOMActivateEvent) { - m_inputType->handleDOMActivateEvent(evt); - if (evt->defaultHandled()) + if (event.type() == eventNames().DOMActivateEvent) { + m_inputType->handleDOMActivateEvent(event); + if (event.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() == eventNames().keypressEvent) { - m_inputType->handleKeypressEvent(static_cast<KeyboardEvent*>(evt)); - if (evt->defaultHandled()) - return; - } - - if (evt->isKeyboardEvent() && evt->type() == eventNames().keyupEvent) { - m_inputType->handleKeyupEvent(static_cast<KeyboardEvent*>(evt)); - if (evt->defaultHandled()) - return; + if (is<KeyboardEvent>(event)) { + KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); + if (keyboardEvent.type() == eventNames().keypressEvent) { + m_inputType->handleKeypressEvent(keyboardEvent); + if (keyboardEvent.defaultHandled()) + return; + } else if (keyboardEvent.type() == eventNames().keyupEvent) { + m_inputType->handleKeyupEvent(keyboardEvent); + if (keyboardEvent.defaultHandled()) + return; + } } - if (m_inputType->shouldSubmitImplicitly(evt)) { + if (m_inputType->shouldSubmitImplicitly(event)) { if (isSearchField()) { addSearchResult(); onSearch(); @@ -1175,29 +1184,27 @@ void HTMLInputElement::defaultEventHandler(Event* evt) if (wasChangedSinceLastFormControlChangeEvent()) dispatchFormControlChangeEvent(); - RefPtr<HTMLFormElement> formForSubmission = m_inputType->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()); + if (auto* formElement = form()) + formElement->submitImplicitly(event, canTriggerImplicitSubmission()); - evt->setDefaultHandled(); + event.setDefaultHandled(); return; } - if (evt->isBeforeTextInsertedEvent()) - m_inputType->handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(evt)); + if (is<BeforeTextInsertedEvent>(event)) + m_inputType->handleBeforeTextInsertedEvent(downcast<BeforeTextInsertedEvent>(event)); - if (evt->isMouseEvent() && evt->type() == eventNames().mousedownEvent) { - m_inputType->handleMouseDownEvent(static_cast<MouseEvent*>(evt)); - if (evt->defaultHandled()) + if (is<MouseEvent>(event) && event.type() == eventNames().mousedownEvent) { + m_inputType->handleMouseDownEvent(downcast<MouseEvent>(event)); + if (event.defaultHandled()) return; } - document().updateStyleIfNeeded(); - m_inputType->forwardEvent(evt); + m_inputType->forwardEvent(event); - if (!callBaseClassEarly && !evt->defaultHandled()) - HTMLTextFormControlElement::defaultEventHandler(evt); + if (!callBaseClassEarly && !event.defaultHandled()) + HTMLTextFormControlElement::defaultEventHandler(event); } bool HTMLInputElement::willRespondToMouseClickEvents() @@ -1215,12 +1222,12 @@ bool HTMLInputElement::isURLAttribute(const Attribute& attribute) const String HTMLInputElement::defaultValue() const { - return fastGetAttribute(valueAttr); + return attributeWithoutSynchronization(valueAttr); } void HTMLInputElement::setDefaultValue(const String &value) { - setAttribute(valueAttr, value); + setAttributeWithoutSynchronization(valueAttr, value); } static inline bool isRFC2616TokenCharacter(UChar ch) @@ -1255,13 +1262,13 @@ static Vector<String> parseAcceptAttribute(const String& acceptString, bool (*pr Vector<String> splitTypes; acceptString.split(',', false, splitTypes); - for (size_t i = 0; i < splitTypes.size(); ++i) { - String trimmedType = stripLeadingAndTrailingHTMLSpaces(splitTypes[i]); + for (auto& splitType : splitTypes) { + String trimmedType = stripLeadingAndTrailingHTMLSpaces(splitType); if (trimmedType.isEmpty()) continue; if (!predicate(trimmedType)) continue; - types.append(trimmedType.lower()); + types.append(trimmedType.convertToASCIILowercase()); } return types; @@ -1269,67 +1276,64 @@ static Vector<String> parseAcceptAttribute(const String& acceptString, bool (*pr Vector<String> HTMLInputElement::acceptMIMETypes() { - return parseAcceptAttribute(fastGetAttribute(acceptAttr), isValidMIMEType); + return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidMIMEType); } Vector<String> HTMLInputElement::acceptFileExtensions() { - return parseAcceptAttribute(fastGetAttribute(acceptAttr), isValidFileExtension); + return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidFileExtension); } String HTMLInputElement::accept() const { - return fastGetAttribute(acceptAttr); + return attributeWithoutSynchronization(acceptAttr); } String HTMLInputElement::alt() const { - return fastGetAttribute(altAttr); -} - -int HTMLInputElement::maxLength() const -{ - return m_maxLength; + return attributeWithoutSynchronization(altAttr); } -void HTMLInputElement::setMaxLength(int maxLength, ExceptionCode& ec) +unsigned HTMLInputElement::effectiveMaxLength() const { - if (maxLength < 0) - ec = INDEX_SIZE_ERR; - else - setIntegralAttribute(maxlengthAttr, maxLength); + // The number -1 represents no maximum at all; conveniently it becomes a super-large value when converted to unsigned. + return std::min<unsigned>(maxLength(), maxEffectiveLength); } bool HTMLInputElement::multiple() const { - return fastHasAttribute(multipleAttr); + return hasAttributeWithoutSynchronization(multipleAttr); } -void HTMLInputElement::setSize(unsigned size) +ExceptionOr<void> HTMLInputElement::setSize(unsigned size) { - setUnsignedIntegralAttribute(sizeAttr, size); + if (!size) + return Exception { INDEX_SIZE_ERR }; + setUnsignedIntegralAttribute(sizeAttr, limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(size, defaultSize)); + return { }; } -void HTMLInputElement::setSize(unsigned size, ExceptionCode& ec) +URL HTMLInputElement::src() const { - if (!size) - ec = INDEX_SIZE_ERR; - else - setSize(size); + return document().completeURL(attributeWithoutSynchronization(srcAttr)); } -URL HTMLInputElement::src() const +void HTMLInputElement::setAutoFilled(bool autoFilled) { - return document().completeURL(fastGetAttribute(srcAttr)); + if (autoFilled == m_isAutoFilled) + return; + + m_isAutoFilled = autoFilled; + invalidateStyleForSubtree(); } -void HTMLInputElement::setAutofilled(bool autofilled) +void HTMLInputElement::setShowAutoFillButton(AutoFillButtonType autoFillButtonType) { - if (autofilled == m_isAutofilled) + if (static_cast<uint8_t>(autoFillButtonType) == m_autoFillButtonType) return; - m_isAutofilled = autofilled; - setNeedsStyleRecalc(); + m_autoFillButtonType = static_cast<uint8_t>(autoFillButtonType); + m_inputType->updateAutoFillButton(); } FileList* HTMLInputElement::files() @@ -1337,9 +1341,9 @@ FileList* HTMLInputElement::files() return m_inputType->files(); } -void HTMLInputElement::setFiles(PassRefPtr<FileList> files) +void HTMLInputElement::setFiles(RefPtr<FileList>&& files) { - m_inputType->setFiles(files); + m_inputType->setFiles(WTFMove(files)); } #if ENABLE(DRAG_SUPPORT) @@ -1396,12 +1400,12 @@ String HTMLInputElement::localizeValue(const String& proposedValue) const bool HTMLInputElement::isInRange() const { - return m_inputType->isInRange(value()); + return willValidate() && m_inputType->isInRange(value()); } bool HTMLInputElement::isOutOfRange() const { - return m_inputType->isOutOfRange(value()); + return willValidate() && m_inputType->isOutOfRange(value()); } bool HTMLInputElement::needsSuspensionCallback() @@ -1422,13 +1426,13 @@ bool HTMLInputElement::needsSuspensionCallback() void HTMLInputElement::registerForSuspensionCallbackIfNeeded() { if (needsSuspensionCallback()) - document().registerForPageCacheSuspensionCallbacks(this); + document().registerForDocumentSuspensionCallbacks(this); } void HTMLInputElement::unregisterForSuspensionCallbackIfNeeded() { if (!needsSuspensionCallback()) - document().unregisterForPageCacheSuspensionCallbacks(this); + document().unregisterForDocumentSuspensionCallbacks(this); } bool HTMLInputElement::isRequiredFormControl() const @@ -1436,11 +1440,6 @@ 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() && !isDisabledOrReadOnly(); @@ -1453,23 +1452,22 @@ void HTMLInputElement::addSearchResult() void HTMLInputElement::onSearch() { - ASSERT(isSearchField()); + // The type of the input element could have changed during event handling. If we are no longer + // a search field, don't try to do search things. + if (!isSearchField()) + return; + if (m_inputType) - static_cast<SearchInputType*>(m_inputType.get())->stopSearchEventTimer(); + downcast<SearchInputType>(*m_inputType.get()).stopSearchEventTimer(); dispatchEvent(Event::create(eventNames().searchEvent, true, false)); } -void HTMLInputElement::updateClearButtonVisibility() -{ - m_inputType->updateClearButtonVisibility(); -} - -void HTMLInputElement::documentDidResumeFromPageCache() +void HTMLInputElement::resumeFromDocumentSuspension() { ASSERT(needsSuspensionCallback()); #if ENABLE(INPUT_TYPE_COLOR) - // <input type=color> uses documentWillSuspendForPageCache to detach the color picker UI, + // <input type=color> uses prepareForDocumentSuspension to detach the color picker UI, // so it should not be reset when being loaded from page cache. if (isColorControl()) return; @@ -1478,7 +1476,7 @@ void HTMLInputElement::documentDidResumeFromPageCache() } #if ENABLE(INPUT_TYPE_COLOR) -void HTMLInputElement::documentWillSuspendForPageCache() +void HTMLInputElement::prepareForDocumentSuspension() { if (!isColorControl()) return; @@ -1502,49 +1500,52 @@ void HTMLInputElement::didChangeForm() Node::InsertionNotificationRequest HTMLInputElement::insertedInto(ContainerNode& insertionPoint) { HTMLTextFormControlElement::insertedInto(insertionPoint); - if (insertionPoint.inDocument() && !form()) - addToRadioButtonGroup(); #if ENABLE(DATALIST_ELEMENT) resetListAttributeTargetObserver(); #endif - return InsertionDone; + return InsertionShouldCallFinishedInsertingSubtree; +} + +void HTMLInputElement::finishedInsertingSubtree() +{ + HTMLTextFormControlElement::finishedInsertingSubtree(); + if (isConnected() && !form()) + addToRadioButtonGroup(); } void HTMLInputElement::removedFrom(ContainerNode& insertionPoint) { - if (insertionPoint.inDocument() && !form()) + if (insertionPoint.isConnected() && !form()) removeFromRadioButtonGroup(); HTMLTextFormControlElement::removedFrom(insertionPoint); - ASSERT(!inDocument()); + ASSERT(!isConnected()); #if ENABLE(DATALIST_ELEMENT) resetListAttributeTargetObserver(); #endif } -void HTMLInputElement::didMoveToNewDocument(Document* oldDocument) +void HTMLInputElement::didMoveToNewDocument(Document& oldDocument) { - if (hasImageLoader()) + if (imageLoader()) imageLoader()->elementDidMoveToNewDocument(); bool needsSuspensionCallback = this->needsSuspensionCallback(); - if (oldDocument) { - // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered - if (needsSuspensionCallback) - oldDocument->unregisterForPageCacheSuspensionCallbacks(this); - if (isRadioButton()) - oldDocument->formController().checkedRadioButtons().removeButton(this); + // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered + if (needsSuspensionCallback) + oldDocument.unregisterForDocumentSuspensionCallbacks(this); + if (isRadioButton()) + oldDocument.formController().radioButtonGroups().removeButton(this); #if ENABLE(TOUCH_EVENTS) - if (m_hasTouchEventHandler) - oldDocument->didRemoveEventTargetNode(this); + if (m_hasTouchEventHandler) + oldDocument.didRemoveEventTargetNode(*this); #endif - } if (needsSuspensionCallback) - document().registerForPageCacheSuspensionCallbacks(this); + document().registerForDocumentSuspensionCallbacks(this); #if ENABLE(TOUCH_EVENTS) if (m_hasTouchEventHandler) - document().didAddTouchEventHandler(this); + document().didAddTouchEventHandler(*this); #endif HTMLTextFormControlElement::didMoveToNewDocument(oldDocument); @@ -1557,28 +1558,29 @@ void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const addSubresourceURL(urls, src()); } -bool HTMLInputElement::recalcWillValidate() const +bool HTMLInputElement::computeWillValidate() const { - return m_inputType->supportsValidation() && HTMLTextFormControlElement::recalcWillValidate(); + return m_inputType->supportsValidation() && HTMLTextFormControlElement::computeWillValidate(); } void HTMLInputElement::requiredAttributeChanged() { HTMLTextFormControlElement::requiredAttributeChanged(); - if (CheckedRadioButtons* buttons = checkedRadioButtons()) + if (RadioButtonGroups* buttons = radioButtonGroups()) buttons->requiredAttributeChanged(this); m_inputType->requiredAttributeChanged(); } -#if ENABLE(INPUT_TYPE_COLOR) -void HTMLInputElement::selectColorInColorChooser(const Color& color) +Color HTMLInputElement::valueAsColor() const { - if (!m_inputType->isColorControl()) - return; - static_cast<ColorInputType*>(m_inputType.get())->didChooseColor(color); + return m_inputType->valueAsColor(); } -#endif - + +void HTMLInputElement::selectColor(const Color& color) +{ + m_inputType->selectColor(color); +} + #if ENABLE(DATALIST_ELEMENT) HTMLElement* HTMLInputElement::list() const { @@ -1588,24 +1590,22 @@ HTMLElement* HTMLInputElement::list() const HTMLDataListElement* HTMLInputElement::dataList() const { if (!m_hasNonEmptyList) - return 0; + return nullptr; if (!m_inputType->shouldRespectListAttribute()) - return 0; + return nullptr; - Element* element = treeScope().getElementById(fastGetAttribute(listAttr)); - if (!element) - return 0; - if (!element->hasTagName(datalistTag)) - return 0; + Element* element = treeScope().getElementById(attributeWithoutSynchronization(listAttr)); + if (!is<HTMLDataListElement>(element)) + return nullptr; - return toHTMLDataListElement(element); + return downcast<HTMLDataListElement>(element); } void HTMLInputElement::resetListAttributeTargetObserver() { - if (inDocument()) - m_listAttributeTargetObserver = ListAttributeTargetObserver::create(fastGetAttribute(listAttr), this); + if (isConnected()) + m_listAttributeTargetObserver = std::make_unique<ListAttributeTargetObserver>(attributeWithoutSynchronization(listAttr), this); else m_listAttributeTargetObserver = nullptr; } @@ -1621,16 +1621,6 @@ bool HTMLInputElement::isSteppable() const return m_inputType->isSteppable(); } -#if ENABLE(INPUT_SPEECH) - -bool HTMLInputElement::isSpeechEnabled() const -{ - // FIXME: Add support for RANGE, EMAIL, URL, COLOR and DATE/TIME input types. - return m_inputType->shouldRespectSpeechAttribute() && RuntimeEnabledFeatures::sharedFeatures().speechInputEnabled() && hasAttribute(webkitspeechAttr); -} - -#endif - #if PLATFORM(IOS) DateComponents::Type HTMLInputElement::dateType() const { @@ -1775,19 +1765,33 @@ void HTMLInputElement::updatePlaceholderText() return m_inputType->updatePlaceholderText(); } -void HTMLInputElement::parseMaxLengthAttribute(const AtomicString& value) +bool HTMLInputElement::isEmptyValue() const +{ + return m_inputType->isEmptyValue(); +} + +void HTMLInputElement::maxLengthAttributeChanged(const AtomicString& newValue) { - int maxLength; - if (!parseHTMLInteger(value, maxLength)) - maxLength = maximumLength; - if (maxLength < 0 || maxLength > maximumLength) - maxLength = maximumLength; - int oldMaxLength = m_maxLength; - m_maxLength = maxLength; - if (oldMaxLength != maxLength) + unsigned oldEffectiveMaxLength = effectiveMaxLength(); + internalSetMaxLength(parseHTMLNonNegativeInteger(newValue).value_or(-1)); + if (oldEffectiveMaxLength != effectiveMaxLength()) updateValueIfNeeded(); - setNeedsStyleRecalc(); - setNeedsValidityCheck(); + + // FIXME: Do we really need to do this if the effective maxLength has not changed? + invalidateStyleForSubtree(); + updateValidity(); +} + +void HTMLInputElement::minLengthAttributeChanged(const AtomicString& newValue) +{ + int oldMinLength = minLength(); + internalSetMinLength(parseHTMLNonNegativeInteger(newValue).value_or(-1)); + if (oldMinLength != minLength()) + updateValueIfNeeded(); + + // FIXME: Do we really need to do this if the effective minLength has not changed? + invalidateStyleForSubtree(); + updateValidity(); } void HTMLInputElement::updateValueIfNeeded() @@ -1803,69 +1807,83 @@ String HTMLInputElement::defaultToolTip() const return m_inputType->defaultToolTip(); } -bool HTMLInputElement::shouldAppearIndeterminate() const +bool HTMLInputElement::matchesIndeterminatePseudoClass() const { - return m_inputType->supportsIndeterminateAppearance() && indeterminate(); + // For input elements, matchesIndeterminatePseudoClass() + // is not equivalent to shouldAppearIndeterminate() because of radio button. + // + // A group of radio button without any checked button is indeterminate + // for the :indeterminate selector. On the other hand, RenderTheme + // currently only supports single element being indeterminate. + // Because of this, radio is indetermindate for CSS but not for render theme. + return m_inputType->matchesIndeterminatePseudoClass(); } -#if ENABLE(MEDIA_CAPTURE) -String HTMLInputElement::capture() const +bool HTMLInputElement::shouldAppearIndeterminate() const { - if (!isFileUpload()) - return String(); - - String capture = fastGetAttribute(captureAttr).lower(); - if (capture == "camera" - || capture == "camcorder" - || capture == "microphone" - || capture == "filesystem") - return capture; - - return "filesystem"; + return m_inputType->shouldAppearIndeterminate(); } -void HTMLInputElement::setCapture(const String& value) +#if ENABLE(MEDIA_CAPTURE) +MediaCaptureType HTMLInputElement::mediaCaptureType() const { - setAttribute(captureAttr, value); + if (!isFileUpload()) + return MediaCaptureTypeNone; + + auto& captureAttribute = attributeWithoutSynchronization(captureAttr); + if (captureAttribute.isNull()) + return MediaCaptureTypeNone; + + if (equalLettersIgnoringASCIICase(captureAttribute, "user")) + return MediaCaptureTypeUser; + + return MediaCaptureTypeEnvironment; } - #endif bool HTMLInputElement::isInRequiredRadioButtonGroup() { ASSERT(isRadioButton()); - if (CheckedRadioButtons* buttons = checkedRadioButtons()) + if (RadioButtonGroups* buttons = radioButtonGroups()) return buttons->isInRequiredGroup(this); return false; } +Vector<HTMLInputElement*> HTMLInputElement::radioButtonGroup() const +{ + RadioButtonGroups* buttons = radioButtonGroups(); + if (!buttons) + return { }; + return buttons->groupMembers(*this); +} + HTMLInputElement* HTMLInputElement::checkedRadioButtonForGroup() const { - if (CheckedRadioButtons* buttons = checkedRadioButtons()) + if (RadioButtonGroups* buttons = radioButtonGroups()) return buttons->checkedButtonForGroup(name()); return 0; } -CheckedRadioButtons* HTMLInputElement::checkedRadioButtons() const +RadioButtonGroups* HTMLInputElement::radioButtonGroups() const { if (!isRadioButton()) - return 0; - if (HTMLFormElement* formElement = form()) - return &formElement->checkedRadioButtons(); - if (inDocument()) - return &document().formController().checkedRadioButtons(); - return 0; + return nullptr; + if (auto* formElement = form()) + return &formElement->radioButtonGroups(); + if (isConnected()) + return &document().formController().radioButtonGroups(); + return nullptr; } inline void HTMLInputElement::addToRadioButtonGroup() { - if (CheckedRadioButtons* buttons = checkedRadioButtons()) + if (RadioButtonGroups* buttons = radioButtonGroups()) buttons->addButton(this); } inline void HTMLInputElement::removeFromRadioButtonGroup() { - if (CheckedRadioButtons* buttons = checkedRadioButtons()) + if (RadioButtonGroups* buttons = radioButtonGroups()) buttons->removeButton(this); } @@ -1890,11 +1908,6 @@ void HTMLInputElement::setWidth(unsigned width) } #if ENABLE(DATALIST_ELEMENT) -OwnPtr<ListAttributeTargetObserver> ListAttributeTargetObserver::create(const AtomicString& id, HTMLInputElement* element) -{ - return adoptPtr(new ListAttributeTargetObserver(id, element)); -} - ListAttributeTargetObserver::ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement* element) : IdTargetObserver(element->treeScope().idTargetObserverRegistry(), id) , m_element(element) @@ -1907,24 +1920,108 @@ void ListAttributeTargetObserver::idTargetChanged() } #endif -void HTMLInputElement::setRangeText(const String& replacement, ExceptionCode& ec) +ExceptionOr<void> HTMLInputElement::setRangeText(const String& replacement) { - if (!m_inputType->supportsSelectionAPI()) { - ec = INVALID_STATE_ERR; - return; - } + if (!m_inputType->supportsSelectionAPI()) + return Exception { INVALID_STATE_ERR }; - HTMLTextFormControlElement::setRangeText(replacement, ec); + return HTMLTextFormControlElement::setRangeText(replacement); } -void HTMLInputElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode, ExceptionCode& ec) +ExceptionOr<void> HTMLInputElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode) { - if (!m_inputType->supportsSelectionAPI()) { - ec = INVALID_STATE_ERR; - return; - } + if (!m_inputType->supportsSelectionAPI()) + return Exception { INVALID_STATE_ERR }; + + return HTMLTextFormControlElement::setRangeText(replacement, start, end, selectionMode); +} + +bool HTMLInputElement::shouldTruncateText(const RenderStyle& style) const +{ + if (!isTextField()) + return false; + return document().focusedElement() != this && style.textOverflow() == TextOverflowEllipsis; +} + +ExceptionOr<int> HTMLInputElement::selectionStartForBindings() const +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + return selectionStart(); +} + +ExceptionOr<void> HTMLInputElement::setSelectionStartForBindings(int start) +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + setSelectionStart(start); + return { }; +} - HTMLTextFormControlElement::setRangeText(replacement, start, end, selectionMode, ec); +ExceptionOr<int> HTMLInputElement::selectionEndForBindings() const +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + return selectionEnd(); +} + +ExceptionOr<void> HTMLInputElement::setSelectionEndForBindings(int end) +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + setSelectionEnd(end); + return { }; +} + +ExceptionOr<String> HTMLInputElement::selectionDirectionForBindings() const +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + return String { selectionDirection() }; +} + +ExceptionOr<void> HTMLInputElement::setSelectionDirectionForBindings(const String& direction) +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + setSelectionDirection(direction); + return { }; +} + +ExceptionOr<void> HTMLInputElement::setSelectionRangeForBindings(int start, int end, const String& direction) +{ + if (!canHaveSelection()) + return Exception { TypeError }; + + setSelectionRange(start, end, direction); + return { }; +} + +RenderStyle HTMLInputElement::createInnerTextStyle(const RenderStyle& style) const +{ + auto textBlockStyle = RenderStyle::create(); + textBlockStyle.inheritFrom(style); + adjustInnerTextStyle(style, textBlockStyle); + + textBlockStyle.setWhiteSpace(PRE); + textBlockStyle.setOverflowWrap(NormalOverflowWrap); + textBlockStyle.setOverflowX(OHIDDEN); + textBlockStyle.setOverflowY(OHIDDEN); + textBlockStyle.setTextOverflow(shouldTruncateText(style) ? TextOverflowEllipsis : TextOverflowClip); + + // Do not allow line-height to be smaller than our default. + if (textBlockStyle.fontMetrics().lineSpacing() > style.computedLineHeight()) + textBlockStyle.setLineHeight(RenderStyle::initialLineHeight()); + + textBlockStyle.setDisplay(BLOCK); + + return textBlockStyle; } #if ENABLE(DATE_AND_TIME_INPUT_TYPES) @@ -1937,7 +2034,8 @@ bool HTMLInputElement::setupDateTimeChooserParameters(DateTimeChooserParameters& parameters.minimum = minimum(); parameters.maximum = maximum(); parameters.required = isRequired(); - if (!RuntimeEnabledFeatures::sharedFeatures().langAttributeAwareFormControlUIEnabled()) + + if (!document().settings().langAttributeAwareFormControlUIEnabled()) parameters.locale = defaultLanguage(); else { AtomicString computedLocale = computeInheritedLanguage(); @@ -1953,13 +2051,16 @@ bool HTMLInputElement::setupDateTimeChooserParameters(DateTimeChooserParameters& parameters.stepBase = 0; } - parameters.anchorRectInRootView = document().view()->contentsToRootView(pixelSnappedBoundingBox()); + if (RenderElement* renderer = this->renderer()) + parameters.anchorRectInRootView = document().view()->contentsToRootView(renderer->absoluteBoundingBoxRect()); + else + parameters.anchorRectInRootView = IntRect(); parameters.currentValue = value(); parameters.isAnchorElementRTL = computedStyle()->direction() == RTL; #if ENABLE(DATALIST_ELEMENT) if (HTMLDataListElement* dataList = this->dataList()) { - RefPtr<HTMLCollection> options = dataList->options(); - for (unsigned i = 0; HTMLOptionElement* option = toHTMLOptionElement(options->item(i)); ++i) { + Ref<HTMLCollection> options = dataList->options(); + for (unsigned i = 0; HTMLOptionElement* option = downcast<HTMLOptionElement>(options->item(i)); ++i) { if (!isValidValue(option->value())) continue; parameters.suggestionValues.append(sanitizeValue(option->value())); @@ -1972,4 +2073,9 @@ bool HTMLInputElement::setupDateTimeChooserParameters(DateTimeChooserParameters& } #endif +void HTMLInputElement::capsLockStateMayHaveChanged() +{ + m_inputType->capsLockStateMayHaveChanged(); +} + } // namespace |