| /* |
| * 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-2021 Apple Inc. All rights reserved. |
| * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
| * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) |
| * Copyright (C) 2010-2021 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 "config.h" |
| #include "HTMLInputElement.h" |
| |
| #include "AXObjectCache.h" |
| #include "BeforeTextInsertedEvent.h" |
| #include "CSSGradientValue.h" |
| #include "CSSPropertyNames.h" |
| #include "CSSValuePool.h" |
| #include "Chrome.h" |
| #include "ChromeClient.h" |
| #include "DateComponents.h" |
| #include "DateTimeChooser.h" |
| #include "DocumentInlines.h" |
| #include "Editor.h" |
| #include "ElementInlines.h" |
| #include "EventNames.h" |
| #include "FileInputType.h" |
| #include "FileList.h" |
| #include "FormController.h" |
| #include "Frame.h" |
| #include "FrameSelection.h" |
| #include "FrameView.h" |
| #include "HTMLDataListElement.h" |
| #include "HTMLFormElement.h" |
| #include "HTMLImageLoader.h" |
| #include "HTMLOptionElement.h" |
| #include "HTMLParserIdioms.h" |
| #include "IdTargetObserver.h" |
| #include "KeyboardEvent.h" |
| #include "LocalizedStrings.h" |
| #include "MouseEvent.h" |
| #include "NodeRenderStyle.h" |
| #include "Page.h" |
| #include "PlatformMouseEvent.h" |
| #include "PseudoClassChangeInvalidation.h" |
| #include "RenderTextControlSingleLine.h" |
| #include "RenderTheme.h" |
| #include "ScopedEventQueue.h" |
| #include "SearchInputType.h" |
| #include "Settings.h" |
| #include "StepRange.h" |
| #include "StyleGeneratedImage.h" |
| #include "TextControlInnerElements.h" |
| #include "TypedElementDescendantIterator.h" |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/Language.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/Ref.h> |
| #include <wtf/text/StringToIntegerConversion.h> |
| |
| #if ENABLE(TOUCH_EVENTS) |
| #include "TouchEvent.h" |
| #endif |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLInputElement); |
| |
| using namespace HTMLNames; |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| class ListAttributeTargetObserver final : public IdTargetObserver { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| ListAttributeTargetObserver(const AtomString& id, HTMLInputElement*); |
| |
| void idTargetChanged() override; |
| |
| private: |
| HTMLInputElement* m_element; |
| }; |
| #endif |
| |
| // 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 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_isChecked(false) |
| , m_dirtyCheckednessFlag(false) |
| , m_isIndeterminate(false) |
| , m_hasType(false) |
| , m_isActivatedSubmit(false) |
| , m_autocomplete(Uninitialized) |
| , m_isAutoFilled(false) |
| , m_isAutoFilledAndViewable(false) |
| , m_isAutoFilledAndObscured(false) |
| , m_autoFillButtonType(static_cast<uint8_t>(AutoFillButtonType::None)) |
| , m_lastAutoFillButtonType(static_cast<uint8_t>(AutoFillButtonType::None)) |
| , m_isAutoFillAvailable(false) |
| #if ENABLE(DATALIST_ELEMENT) |
| , m_hasNonEmptyList(false) |
| #endif |
| , m_stateRestored(false) |
| , m_parsingInProgress(createdByParser) |
| , m_valueAttributeWasUpdatedAfterParsing(false) |
| , m_wasModifiedByUser(false) |
| , m_canReceiveDroppedFiles(false) |
| #if ENABLE(TOUCH_EVENTS) |
| , m_hasTouchEventHandler(false) |
| #endif |
| , 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'. |
| if (!createdByParser) |
| m_inputType = InputType::createText(*this); |
| |
| ASSERT(hasTagName(inputTag)); |
| setHasCustomStyleResolveCallbacks(); |
| } |
| |
| Ref<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) |
| { |
| bool shouldCreateShadowRootLazily = createdByParser; |
| Ref<HTMLInputElement> inputElement = adoptRef(*new HTMLInputElement(tagName, document, form, createdByParser)); |
| if (!shouldCreateShadowRootLazily) { |
| ASSERT(inputElement->m_inputType->needsShadowSubtree()); |
| inputElement->createUserAgentShadowRoot(); |
| inputElement->createShadowSubtreeAndUpdateInnerTextElementEditability(); |
| } |
| return inputElement; |
| } |
| |
| HTMLImageLoader& HTMLInputElement::ensureImageLoader() |
| { |
| if (!m_imageLoader) |
| m_imageLoader = makeUnique<HTMLImageLoader>(*this); |
| return *m_imageLoader; |
| } |
| |
| void HTMLInputElement::createShadowSubtreeAndUpdateInnerTextElementEditability() |
| { |
| Ref<InputType> protectedInputType(*m_inputType); |
| protectedInputType->createShadowSubtreeAndUpdateInnerTextElementEditability(m_parsingInProgress ? ChildChange::Source::Parser : ChildChange::Source::API, isInnerTextElementEditable()); |
| } |
| |
| HTMLInputElement::~HTMLInputElement() |
| { |
| if (needsSuspensionCallback()) |
| document().unregisterForDocumentSuspensionCallbacks(*this); |
| |
| // Need to remove form association while this is still an HTMLInputElement |
| // so that virtual functions are called correctly. |
| setForm(nullptr); |
| |
| // This is needed for a radio button that was not in a form, and also for |
| // a radio button that was in a form. The call to setForm(nullptr) above |
| // actually adds the button to the document groups in the latter case. |
| // That is inelegant, but harmless since we remove it here. |
| if (isRadioButton()) |
| treeScope().radioButtonGroups().removeButton(*this); |
| |
| #if ENABLE(TOUCH_EVENTS) |
| if (m_hasTouchEventHandler) |
| document().didRemoveEventTargetNode(*this); |
| #endif |
| } |
| |
| const AtomString& HTMLInputElement::name() const |
| { |
| return m_name.isNull() ? emptyAtom() : m_name; |
| } |
| |
| Vector<FileChooserFileInfo> HTMLInputElement::filesFromFileInputFormControlState(const FormControlState& state) |
| { |
| return FileInputType::filesFromFormControlState(state); |
| } |
| |
| HTMLElement* HTMLInputElement::containerElement() const |
| { |
| return m_inputType->containerElement(); |
| } |
| |
| RefPtr<TextControlInnerTextElement> HTMLInputElement::innerTextElement() const |
| { |
| return m_inputType->innerTextElement(); |
| } |
| |
| HTMLElement* HTMLInputElement::innerBlockElement() const |
| { |
| return m_inputType->innerBlockElement(); |
| } |
| |
| 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(); |
| } |
| |
| HTMLElement* HTMLInputElement::cancelButtonElement() const |
| { |
| return m_inputType->cancelButtonElement(); |
| } |
| |
| HTMLElement* HTMLInputElement::sliderThumbElement() const |
| { |
| return m_inputType->sliderThumbElement(); |
| } |
| |
| HTMLElement* HTMLInputElement::sliderTrackElement() const |
| { |
| return m_inputType->sliderTrackElement(); |
| } |
| |
| HTMLElement* HTMLInputElement::placeholderElement() const |
| { |
| return m_inputType->placeholderElement(); |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| HTMLElement* HTMLInputElement::dataListButtonElement() const |
| { |
| return m_inputType->dataListButtonElement(); |
| } |
| #endif |
| |
| 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()) { |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| return !m_inputType->typeMismatchFor(value) |
| && !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 tooShort(value(), CheckDirtyFlag); |
| } |
| |
| bool HTMLInputElement::tooLong() const |
| { |
| return tooLong(value(), CheckDirtyFlag); |
| } |
| |
| bool HTMLInputElement::typeMismatch() const |
| { |
| return m_inputType->typeMismatch(); |
| } |
| |
| bool HTMLInputElement::valueMissing() const |
| { |
| return m_inputType->valueMissing(value()); |
| } |
| |
| bool HTMLInputElement::hasBadInput() const |
| { |
| return m_inputType->hasBadInput(); |
| } |
| |
| bool HTMLInputElement::patternMismatch() const |
| { |
| return m_inputType->patternMismatch(value()); |
| } |
| |
| bool HTMLInputElement::tooShort(StringView value, NeedsToCheckDirtyFlag check) const |
| { |
| if (!supportsMinLength()) |
| return false; |
| |
| 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; |
| } |
| // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length. |
| return numGraphemeClusters(value) > max; |
| } |
| |
| bool HTMLInputElement::rangeUnderflow() const |
| { |
| return m_inputType->rangeUnderflow(value()); |
| } |
| |
| bool HTMLInputElement::rangeOverflow() const |
| { |
| return m_inputType->rangeOverflow(value()); |
| } |
| |
| String HTMLInputElement::validationMessage() const |
| { |
| if (!willValidate()) |
| return String(); |
| |
| if (customError()) |
| return customValidationMessage(); |
| |
| return m_inputType->validationMessage(); |
| } |
| |
| double HTMLInputElement::minimum() const |
| { |
| return m_inputType->minimum(); |
| } |
| |
| double HTMLInputElement::maximum() const |
| { |
| return m_inputType->maximum(); |
| } |
| |
| bool HTMLInputElement::stepMismatch() const |
| { |
| return m_inputType->stepMismatch(value()); |
| } |
| |
| bool HTMLInputElement::computeValidity() const |
| { |
| String value = this->value(); |
| bool someError = m_inputType->isInvalid(value) || tooShort(value, CheckDirtyFlag) || tooLong(value, CheckDirtyFlag) || customError(); |
| return !someError; |
| } |
| |
| bool HTMLInputElement::getAllowedValueStep(Decimal* step) const |
| { |
| return m_inputType->getAllowedValueStep(step); |
| } |
| |
| StepRange HTMLInputElement::createStepRange(AnyStepHandling anyStepHandling) const |
| { |
| return m_inputType->createStepRange(anyStepHandling); |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| std::optional<Decimal> HTMLInputElement::findClosestTickMarkValue(const Decimal& value) |
| { |
| return m_inputType->findClosestTickMarkValue(value); |
| } |
| |
| std::optional<double> HTMLInputElement::listOptionValueAsDouble(const HTMLOptionElement& optionElement) |
| { |
| auto optionValue = optionElement.value(); |
| if (!isValidValue(optionValue)) |
| return std::nullopt; |
| |
| return parseToDoubleForNumberType(sanitizeValue(optionValue)); |
| } |
| #endif |
| |
| ExceptionOr<void> HTMLInputElement::stepUp(int n) |
| { |
| return m_inputType->stepUp(n); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::stepDown(int n) |
| { |
| return m_inputType->stepUp(-n); |
| } |
| |
| void HTMLInputElement::blur() |
| { |
| m_inputType->blur(); |
| } |
| |
| void HTMLInputElement::defaultBlur() |
| { |
| HTMLTextFormControlElement::blur(); |
| } |
| |
| bool HTMLInputElement::hasCustomFocusLogic() const |
| { |
| return m_inputType->hasCustomFocusLogic(); |
| } |
| |
| int HTMLInputElement::defaultTabIndex() const |
| { |
| return 0; |
| } |
| |
| bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const |
| { |
| return m_inputType->isKeyboardFocusable(event); |
| } |
| |
| bool HTMLInputElement::isMouseFocusable() const |
| { |
| return m_inputType->isMouseFocusable(); |
| } |
| |
| bool HTMLInputElement::isInteractiveContent() const |
| { |
| return m_inputType->isInteractiveContent(); |
| } |
| |
| bool HTMLInputElement::isTextFormControlFocusable() const |
| { |
| return HTMLTextFormControlElement::isFocusable(); |
| } |
| |
| bool HTMLInputElement::isTextFormControlKeyboardFocusable(KeyboardEvent* event) const |
| { |
| return HTMLTextFormControlElement::isKeyboardFocusable(event); |
| } |
| |
| bool HTMLInputElement::isTextFormControlMouseFocusable() const |
| { |
| return HTMLTextFormControlElement::isMouseFocusable(); |
| } |
| |
| void HTMLInputElement::updateFocusAppearance(SelectionRestorationMode restorationMode, SelectionRevealMode revealMode) |
| { |
| if (isTextField()) { |
| if (restorationMode == SelectionRestorationMode::RestoreOrSelectAll && hasCachedSelection()) |
| restoreCachedSelection(revealMode); |
| else |
| setDefaultSelectionAfterFocus(restorationMode, revealMode); |
| } else |
| HTMLTextFormControlElement::updateFocusAppearance(restorationMode, revealMode); |
| } |
| |
| void HTMLInputElement::setDefaultSelectionAfterFocus(SelectionRestorationMode restorationMode, SelectionRevealMode revealMode) |
| { |
| ASSERT(isTextField()); |
| int start = 0; |
| auto direction = SelectionHasNoDirection; |
| auto* frame = document().frame(); |
| if (frame && frame->editor().behavior().shouldMoveSelectionToEndWhenFocusingTextInput()) { |
| start = std::numeric_limits<int>::max(); |
| direction = SelectionHasForwardDirection; |
| } |
| int end = restorationMode == SelectionRestorationMode::PlaceCaretAtStart ? start : std::numeric_limits<int>::max(); |
| setSelectionRange(start, end, direction, revealMode, Element::defaultFocusTextStateChangeIntent()); |
| } |
| |
| void HTMLInputElement::endEditing() |
| { |
| if (!isTextField()) |
| return; |
| |
| if (RefPtr<Frame> frame = document().frame()) |
| frame->editor().textFieldDidEndEditing(this); |
| } |
| |
| bool HTMLInputElement::shouldUseInputMethod() |
| { |
| return m_inputType->shouldUseInputMethod(); |
| } |
| |
| void HTMLInputElement::handleFocusEvent(Node* oldFocusedNode, FocusDirection direction) |
| { |
| m_inputType->handleFocusEvent(oldFocusedNode, direction); |
| |
| invalidateStyleOnFocusChangeIfNeeded(); |
| } |
| |
| void HTMLInputElement::handleBlurEvent() |
| { |
| m_inputType->handleBlurEvent(); |
| |
| invalidateStyleOnFocusChangeIfNeeded(); |
| } |
| |
| void HTMLInputElement::setType(const AtomString& type) |
| { |
| setAttributeWithoutSynchronization(typeAttr, type); |
| } |
| |
| void HTMLInputElement::resignStrongPasswordAppearance() |
| { |
| if (!hasAutoFillStrongPasswordButton()) |
| return; |
| setAutoFilled(false); |
| setAutoFilledAndViewable(false); |
| setShowAutoFillButton(AutoFillButtonType::None); |
| if (auto* page = document().page()) |
| page->chrome().client().inputElementDidResignStrongPasswordAppearance(*this); |
| } |
| |
| void HTMLInputElement::updateType() |
| { |
| ASSERT(m_inputType); |
| auto newType = InputType::create(*this, attributeWithoutSynchronization(typeAttr)); |
| m_hasType = true; |
| if (m_inputType->formControlType() == newType->formControlType()) |
| return; |
| |
| removeFromRadioButtonGroup(); |
| resignStrongPasswordAppearance(); |
| |
| bool didStoreValue = m_inputType->storesValueSeparateFromAttribute(); |
| bool willStoreValue = newType->storesValueSeparateFromAttribute(); |
| bool neededSuspensionCallback = needsSuspensionCallback(); |
| bool didRespectHeightAndWidth = m_inputType->shouldRespectHeightAndWidthAttributes(); |
| bool wasSuccessfulSubmitButtonCandidate = m_inputType->canBeSuccessfulSubmitButton(); |
| |
| if (didStoreValue && !willStoreValue && hasDirtyValue()) { |
| setAttributeWithoutSynchronization(valueAttr, m_valueIfDirty); |
| m_valueIfDirty = String(); |
| } |
| |
| m_inputType->destroyShadowSubtree(); |
| m_inputType->detachFromElement(); |
| |
| m_inputType = WTFMove(newType); |
| if (m_inputType->needsShadowSubtree()) { |
| ensureUserAgentShadowRoot(); |
| createShadowSubtreeAndUpdateInnerTextElementEditability(); |
| } |
| |
| updateWillValidateAndValidity(); |
| |
| if (!didStoreValue && willStoreValue) |
| m_valueIfDirty = sanitizeValue(attributeWithoutSynchronization(valueAttr)); |
| else |
| updateValueIfNeeded(); |
| |
| setFormControlValueMatchesRenderer(false); |
| m_inputType->updateInnerTextValue(); |
| |
| m_wasModifiedByUser = false; |
| |
| if (neededSuspensionCallback) |
| unregisterForSuspensionCallbackIfNeeded(); |
| else |
| registerForSuspensionCallbackIfNeeded(); |
| |
| if (didRespectHeightAndWidth != m_inputType->shouldRespectHeightAndWidthAttributes()) { |
| ASSERT(elementData()); |
| // FIXME: We don't have the old attribute values so we pretend that we didn't have the old values. |
| if (const Attribute* height = findAttributeByName(heightAttr)) |
| attributeChanged(heightAttr, nullAtom(), height->value()); |
| if (const Attribute* width = findAttributeByName(widthAttr)) |
| attributeChanged(widthAttr, nullAtom(), width->value()); |
| if (const Attribute* align = findAttributeByName(alignAttr)) |
| 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()) |
| invalidateStyleAndRenderersForSubtree(); |
| |
| if (document().focusedElement() == this) |
| updateFocusAppearance(SelectionRestorationMode::RestoreOrSelectAll, SelectionRevealMode::Reveal); |
| |
| setChangedSinceLastFormControlChangeEvent(false); |
| |
| addToRadioButtonGroup(); |
| |
| updateValidity(); |
| } |
| |
| void HTMLInputElement::subtreeHasChanged() |
| { |
| m_inputType->subtreeHasChanged(); |
| // When typing in an input field, childrenChanged is not called, so we need to force the directionality check. |
| calculateAndAdjustDirectionality(); |
| } |
| |
| const AtomString& 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_inputType->saveFormControlState(); |
| } |
| |
| void HTMLInputElement::restoreFormControlState(const FormControlState& state) |
| { |
| m_inputType->restoreFormControlState(state); |
| m_stateRestored = true; |
| } |
| |
| bool HTMLInputElement::canStartSelection() const |
| { |
| if (!isTextField()) |
| return false; |
| return HTMLTextFormControlElement::canStartSelection(); |
| } |
| |
| bool HTMLInputElement::canHaveSelection() const |
| { |
| return isTextField(); |
| } |
| |
| bool HTMLInputElement::accessKeyAction(bool sendMouseEvents) |
| { |
| Ref<InputType> protectedInputType(*m_inputType); |
| return protectedInputType->accessKeyAction(sendMouseEvents); |
| } |
| |
| bool HTMLInputElement::hasPresentationalHintsForAttribute(const QualifiedName& name) const |
| { |
| if (name == vspaceAttr || name == hspaceAttr || name == widthAttr || name == heightAttr || (name == borderAttr && isImageButton())) |
| return true; |
| return HTMLTextFormControlElement::hasPresentationalHintsForAttribute(name); |
| } |
| |
| void HTMLInputElement::collectPresentationalHintsForAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& 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); |
| if (isImageButton()) |
| applyAspectRatioFromWidthAndHeightAttributesToStyle(value, attributeWithoutSynchronization(heightAttr), style); |
| } else if (name == heightAttr) { |
| if (m_inputType->shouldRespectHeightAndWidthAttributes()) |
| addHTMLLengthToStyle(style, CSSPropertyHeight, value); |
| if (isImageButton()) |
| applyAspectRatioFromWidthAndHeightAttributesToStyle(attributeWithoutSynchronization(widthAttr), value, style); |
| } else if (name == borderAttr && isImageButton()) |
| applyBorderAttributeToStyle(value, style); |
| else |
| HTMLTextFormControlElement::collectPresentationalHintsForAttribute(name, value, style); |
| } |
| |
| inline void HTMLInputElement::initializeInputType() |
| { |
| ASSERT(m_parsingInProgress); |
| ASSERT(!m_inputType); |
| |
| const AtomString& type = attributeWithoutSynchronization(typeAttr); |
| if (type.isNull()) { |
| m_inputType = InputType::createText(*this); |
| ASSERT(m_inputType->needsShadowSubtree()); |
| createUserAgentShadowRoot(); |
| createShadowSubtreeAndUpdateInnerTextElementEditability(); |
| updateWillValidateAndValidity(); |
| return; |
| } |
| |
| m_hasType = true; |
| m_inputType = InputType::create(*this, type); |
| if (m_inputType->needsShadowSubtree()) { |
| createUserAgentShadowRoot(); |
| createShadowSubtreeAndUpdateInnerTextElementEditability(); |
| } |
| updateWillValidateAndValidity(); |
| registerForSuspensionCallbackIfNeeded(); |
| runPostTypeUpdateTasks(); |
| } |
| |
| void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomString& value) |
| { |
| ASSERT(m_inputType); |
| Ref<InputType> protectedInputType(*m_inputType); |
| |
| if (name == nameAttr) { |
| removeFromRadioButtonGroup(); |
| m_name = value; |
| addToRadioButtonGroup(); |
| HTMLTextFormControlElement::parseAttribute(name, value); |
| } else if (name == autocompleteAttr) { |
| if (equalLettersIgnoringASCIICase(value, "off")) { |
| m_autocomplete = Off; |
| registerForSuspensionCallbackIfNeeded(); |
| } else { |
| bool needsToUnregister = m_autocomplete == Off; |
| |
| if (value.isEmpty()) |
| m_autocomplete = Uninitialized; |
| else |
| m_autocomplete = On; |
| |
| if (needsToUnregister) |
| unregisterForSuspensionCallbackIfNeeded(); |
| } |
| } else if (name == typeAttr) |
| updateType(); |
| else if (name == valueAttr) { |
| // Changes to the value attribute may change whether or not this element has a default value. |
| // If this field is autocomplete=off that might affect the return value of needsSuspensionCallback. |
| if (m_autocomplete == Off) { |
| unregisterForSuspensionCallbackIfNeeded(); |
| registerForSuspensionCallbackIfNeeded(); |
| } |
| // We only need to setChanged if the form is looking at the default value right now. |
| if (!hasDirtyValue()) { |
| updatePlaceholderVisibility(); |
| invalidateStyleForSubtree(); |
| setFormControlValueMatchesRenderer(false); |
| } |
| updateValidity(); |
| m_valueAttributeWasUpdatedAfterParsing = !m_parsingInProgress; |
| } 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 |
| // finishParsingChildren() is called if parsing is in progress. |
| if ((!m_parsingInProgress || !document().formController().hasFormStateToRestore()) && !m_dirtyCheckednessFlag) { |
| setChecked(!value.isNull()); |
| // setChecked() above sets the dirty checkedness flag so we need to reset it. |
| m_dirtyCheckednessFlag = false; |
| } |
| } else if (name == maxlengthAttr) |
| maxLengthAttributeChanged(value); |
| else if (name == minlengthAttr) |
| minLengthAttributeChanged(value); |
| else if (name == sizeAttr) { |
| unsigned oldSize = m_size; |
| m_size = limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(value, defaultSize); |
| if (m_size != oldSize && renderer()) |
| renderer()->setNeedsLayoutAndPrefWidthsRecalc(); |
| } else if (name == resultsAttr) |
| m_maxResults = value.isNull() ? -1 : std::min(parseHTMLInteger(value).value_or(0), maxSavedResults); |
| else if (name == autosaveAttr || name == incrementalAttr) |
| invalidateStyleForSubtree(); |
| else if (name == maxAttr || name == minAttr || name == multipleAttr || name == patternAttr || name == precisionAttr || name == stepAttr) |
| updateValidity(); |
| #if ENABLE(DATALIST_ELEMENT) |
| else if (name == listAttr) { |
| m_hasNonEmptyList = !value.isEmpty(); |
| if (m_hasNonEmptyList) { |
| resetListAttributeTargetObserver(); |
| dataListMayHaveChanged(); |
| } |
| } |
| #endif |
| else |
| HTMLTextFormControlElement::parseAttribute(name, value); |
| |
| m_inputType->attributeChanged(name); |
| } |
| |
| void HTMLInputElement::disabledStateChanged() |
| { |
| HTMLTextFormControlElement::disabledStateChanged(); |
| m_inputType->disabledStateChanged(); |
| } |
| |
| void HTMLInputElement::readOnlyStateChanged() |
| { |
| HTMLTextFormControlElement::readOnlyStateChanged(); |
| m_inputType->readOnlyStateChanged(); |
| } |
| |
| void HTMLInputElement::parserDidSetAttributes() |
| { |
| DelayedUpdateValidityScope delayedUpdateValidityScope(*this); |
| |
| ASSERT(m_parsingInProgress); |
| initializeInputType(); |
| } |
| |
| void HTMLInputElement::finishParsingChildren() |
| { |
| m_parsingInProgress = false; |
| ASSERT(m_inputType); |
| HTMLTextFormControlElement::finishParsingChildren(); |
| if (!m_stateRestored) { |
| bool checked = hasAttributeWithoutSynchronization(checkedAttr); |
| if (checked) |
| setChecked(checked); |
| m_dirtyCheckednessFlag = false; |
| } |
| } |
| |
| bool HTMLInputElement::rendererIsNeeded(const RenderStyle& style) |
| { |
| return m_inputType->rendererIsNeeded() && HTMLTextFormControlElement::rendererIsNeeded(style); |
| } |
| |
| RenderPtr<RenderElement> HTMLInputElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
| { |
| return m_inputType->createInputRenderer(WTFMove(style)); |
| } |
| |
| void HTMLInputElement::willAttachRenderers() |
| { |
| if (!m_hasType) |
| updateType(); |
| } |
| |
| void HTMLInputElement::didAttachRenderers() |
| { |
| HTMLTextFormControlElement::didAttachRenderers(); |
| |
| m_inputType->attach(); |
| |
| if (document().focusedElement() == this) { |
| document().view()->queuePostLayoutCallback([protectedThis = Ref { *this }] { |
| protectedThis->updateFocusAppearance(SelectionRestorationMode::RestoreOrSelectAll, SelectionRevealMode::Reveal); |
| }); |
| } |
| } |
| |
| void HTMLInputElement::didDetachRenderers() |
| { |
| setFormControlValueMatchesRenderer(false); |
| m_inputType->detach(); |
| } |
| |
| 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 = attributeWithoutSynchronization(altAttr); |
| // fall back to title attribute |
| if (alt.isNull()) |
| alt = attributeWithoutSynchronization(titleAttr); |
| if (alt.isNull()) |
| alt = attributeWithoutSynchronization(valueAttr); |
| if (alt.isEmpty()) |
| alt = inputElementAltText(); |
| return alt; |
| } |
| |
| bool HTMLInputElement::isSuccessfulSubmitButton() const |
| { |
| // HTML spec says that buttons must have names to be considered successful. |
| // However, other browsers do not impose this constraint. So we do not. |
| 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; |
| } |
| |
| void HTMLInputElement::setActivatedSubmit(bool flag) |
| { |
| m_isActivatedSubmit = flag; |
| } |
| |
| bool HTMLInputElement::appendFormData(DOMFormData& formData) |
| { |
| Ref<InputType> protectedInputType(*m_inputType); |
| return m_inputType->isFormDataAppendable() && m_inputType->appendFormData(formData); |
| } |
| |
| void HTMLInputElement::reset() |
| { |
| if (m_inputType->storesValueSeparateFromAttribute()) |
| setValue(String()); |
| |
| setAutoFilled(false); |
| setAutoFilledAndViewable(false); |
| setAutoFilledAndObscured(false); |
| setShowAutoFillButton(AutoFillButtonType::None); |
| setChecked(hasAttributeWithoutSynchronization(checkedAttr)); |
| m_dirtyCheckednessFlag = false; |
| } |
| |
| bool HTMLInputElement::isTextField() const |
| { |
| return m_inputType->isTextField(); |
| } |
| |
| bool HTMLInputElement::isTextType() const |
| { |
| return m_inputType->isTextType(); |
| } |
| |
| void HTMLInputElement::setChecked(bool isChecked) |
| { |
| if (checked() == isChecked) |
| return; |
| |
| m_inputType->willUpdateCheckedness(isChecked); |
| |
| Style::PseudoClassChangeInvalidation checkedInvalidation(*this, CSSSelector::PseudoClassChecked, isChecked); |
| |
| m_dirtyCheckednessFlag = true; |
| m_isChecked = isChecked; |
| |
| if (RadioButtonGroups* buttons = radioButtonGroups()) |
| buttons->updateCheckedState(*this); |
| if (renderer() && renderer()->style().hasEffectiveAppearance()) |
| renderer()->theme().stateChanged(*renderer(), ControlStates::States::Checked); |
| updateValidity(); |
| |
| // Ideally we'd do this from the render tree (matching |
| // RenderTextView), but it's not possible to do it at the moment |
| // because of the way the code is structured. |
| if (renderer()) { |
| if (AXObjectCache* cache = renderer()->document().existingAXObjectCache()) |
| cache->checkedStateChanged(this); |
| } |
| } |
| |
| void HTMLInputElement::setIndeterminate(bool newValue) |
| { |
| if (indeterminate() == newValue) |
| return; |
| |
| m_isIndeterminate = newValue; |
| |
| invalidateStyleForSubtree(); |
| |
| if (renderer() && renderer()->style().hasEffectiveAppearance()) |
| renderer()->theme().stateChanged(*renderer(), ControlStates::States::Checked); |
| } |
| |
| unsigned HTMLInputElement::size() const |
| { |
| return m_size; |
| } |
| |
| bool HTMLInputElement::sizeShouldIncludeDecoration(int& preferredSize) const |
| { |
| return m_inputType->sizeShouldIncludeDecoration(defaultSize, preferredSize); |
| } |
| |
| float HTMLInputElement::decorationWidth() const |
| { |
| return m_inputType->decorationWidth(); |
| } |
| |
| void HTMLInputElement::copyNonAttributePropertiesFromElement(const Element& source) |
| { |
| auto& sourceElement = downcast<HTMLInputElement>(source); |
| |
| m_valueIfDirty = sourceElement.m_valueIfDirty; |
| m_wasModifiedByUser = false; |
| setChecked(sourceElement.m_isChecked); |
| m_dirtyCheckednessFlag = sourceElement.m_dirtyCheckednessFlag; |
| m_isIndeterminate = sourceElement.m_isIndeterminate; |
| |
| HTMLTextFormControlElement::copyNonAttributePropertiesFromElement(source); |
| |
| updateValidity(); |
| setFormControlValueMatchesRenderer(false); |
| m_inputType->updateInnerTextValue(); |
| } |
| |
| String HTMLInputElement::value() const |
| { |
| String value; |
| if (m_inputType->canHaveTypeSpecificValue() && m_inputType->getTypeSpecificValue(value)) |
| return value; |
| |
| value = m_valueIfDirty; |
| if (!value.isNull()) |
| return value; |
| |
| auto& valueString = attributeWithoutSynchronization(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); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setValue(const String& value, TextFieldEventBehavior eventBehavior) |
| { |
| if (isFileUpload() && !value.isEmpty()) |
| return Exception { InvalidStateError }; |
| |
| if (!m_inputType->canSetValue(value)) |
| return { }; |
| |
| Ref<HTMLInputElement> protectedThis(*this); |
| EventQueueScope scope; |
| String sanitizedValue = sanitizeValue(value); |
| bool valueChanged = sanitizedValue != this->value(); |
| |
| setLastChangeWasNotUserEdit(); |
| setFormControlValueMatchesRenderer(false); |
| m_inputType->setValue(sanitizedValue, valueChanged, eventBehavior); |
| |
| bool wasModifiedProgrammatically = eventBehavior == DispatchNoEvent; |
| if (wasModifiedProgrammatically) |
| resignStrongPasswordAppearance(); |
| return { }; |
| } |
| |
| void HTMLInputElement::setValueInternal(const String& sanitizedValue, TextFieldEventBehavior eventBehavior) |
| { |
| m_valueIfDirty = sanitizedValue; |
| m_wasModifiedByUser = eventBehavior != DispatchNoEvent; |
| updateValidity(); |
| } |
| |
| WallTime HTMLInputElement::valueAsDate() const |
| { |
| return m_inputType->valueAsDate(); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setValueAsDate(WallTime value) |
| { |
| return m_inputType->setValueAsDate(value); |
| } |
| |
| double HTMLInputElement::valueAsNumber() const |
| { |
| return m_inputType->valueAsDouble(); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setValueAsNumber(double newValue, TextFieldEventBehavior eventBehavior) |
| { |
| if (!std::isfinite(newValue)) |
| return Exception { NotSupportedError }; |
| return m_inputType->setValueAsDouble(newValue, eventBehavior); |
| } |
| |
| void HTMLInputElement::setValueFromRenderer(const String& value) |
| { |
| // File upload controls will never use this. |
| ASSERT(!isFileUpload()); |
| |
| // Renderer and our event handler are responsible for sanitizing values. |
| // 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 by removing the expression |
| // that calls isEmpty. |
| // http://bugs.webkit.org/show_bug.cgi?id=9661 |
| m_valueIfDirty = value == "\n" ? emptyString() : value; |
| |
| setFormControlValueMatchesRenderer(true); |
| m_wasModifiedByUser = true; |
| |
| // Input event is fired by the Node::defaultEventHandler for editable controls. |
| if (!isTextField()) |
| dispatchInputEvent(); |
| |
| updateValidity(); |
| |
| // We clear certain AutoFill flags here because this catches user edits. |
| setAutoFilled(false); |
| if (m_isAutoFilledAndViewable && value.isEmpty()) |
| setAutoFilledAndViewable(false); |
| } |
| |
| void HTMLInputElement::willDispatchEvent(Event& event, InputElementClickState& state) |
| { |
| if (event.type() == eventNames().textInputEvent && m_inputType->shouldSubmitImplicitly(event)) |
| event.stopPropagation(); |
| if (event.type() == eventNames().clickEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { |
| m_inputType->willDispatchClick(state); |
| state.stateful = true; |
| } |
| } |
| |
| void HTMLInputElement::didDispatchClickEvent(Event& event, const InputElementClickState& state) |
| { |
| m_inputType->didDispatchClick(event, state); |
| } |
| |
| void HTMLInputElement::didBlur() |
| { |
| m_inputType->elementDidBlur(); |
| } |
| |
| void HTMLInputElement::defaultEventHandler(Event& event) |
| { |
| 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 (is<TouchEvent>(event)) { |
| m_inputType->handleTouchEvent(downcast<TouchEvent>(event)); |
| if (event.defaultHandled()) |
| return; |
| } |
| #endif |
| |
| if (is<KeyboardEvent>(event) && event.type() == eventNames().keydownEvent) { |
| auto shouldCallBaseEventHandler = m_inputType->handleKeydownEvent(downcast<KeyboardEvent>(event)); |
| if (event.defaultHandled() || shouldCallBaseEventHandler == InputType::ShouldCallBaseEventHandler::No) |
| 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() && (event.type() == eventNames().keydownEvent || event.type() == eventNames().keypressEvent); |
| if (callBaseClassEarly) { |
| HTMLTextFormControlElement::defaultEventHandler(event); |
| if (event.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 (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 (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(event)) { |
| if (isSearchField()) { |
| addSearchResult(); |
| onSearch(); |
| } |
| // Form submission finishes editing, just as loss of focus does. |
| // If there was a change, send the event now. |
| if (wasChangedSinceLastFormControlChangeEvent()) |
| dispatchFormControlChangeEvent(); |
| |
| // Form may never have been present, or may have been destroyed by code responding to the change event. |
| if (RefPtr formElement = form()) |
| formElement->submitImplicitly(event, canTriggerImplicitSubmission()); |
| |
| event.setDefaultHandled(); |
| return; |
| } |
| |
| if (is<BeforeTextInsertedEvent>(event)) |
| m_inputType->handleBeforeTextInsertedEvent(downcast<BeforeTextInsertedEvent>(event)); |
| |
| if (is<MouseEvent>(event) && event.type() == eventNames().mousedownEvent) { |
| m_inputType->handleMouseDownEvent(downcast<MouseEvent>(event)); |
| if (event.defaultHandled()) |
| return; |
| } |
| |
| m_inputType->forwardEvent(event); |
| |
| if (!callBaseClassEarly && !event.defaultHandled()) |
| HTMLTextFormControlElement::defaultEventHandler(event); |
| } |
| |
| bool HTMLInputElement::willRespondToMouseClickEvents() |
| { |
| if (!isDisabledFormControl()) |
| return true; |
| |
| return HTMLTextFormControlElement::willRespondToMouseClickEvents(); |
| } |
| |
| bool HTMLInputElement::isURLAttribute(const Attribute& attribute) const |
| { |
| return attribute.name() == srcAttr || attribute.name() == formactionAttr || HTMLTextFormControlElement::isURLAttribute(attribute); |
| } |
| |
| String HTMLInputElement::defaultValue() const |
| { |
| return attributeWithoutSynchronization(valueAttr); |
| } |
| |
| void HTMLInputElement::setDefaultValue(const String &value) |
| { |
| setAttributeWithoutSynchronization(valueAttr, value); |
| } |
| |
| 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 == notFound || !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; |
| |
| for (auto& splitType : acceptString.split(',')) { |
| String trimmedType = stripLeadingAndTrailingHTMLSpaces(splitType); |
| if (trimmedType.isEmpty()) |
| continue; |
| if (!predicate(trimmedType)) |
| continue; |
| types.append(trimmedType.convertToASCIILowercase()); |
| } |
| |
| return types; |
| } |
| |
| Vector<String> HTMLInputElement::acceptMIMETypes() |
| { |
| return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidMIMEType); |
| } |
| |
| Vector<String> HTMLInputElement::acceptFileExtensions() |
| { |
| return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidFileExtension); |
| } |
| |
| String HTMLInputElement::accept() const |
| { |
| return attributeWithoutSynchronization(acceptAttr); |
| } |
| |
| String HTMLInputElement::alt() const |
| { |
| return attributeWithoutSynchronization(altAttr); |
| } |
| |
| unsigned HTMLInputElement::effectiveMaxLength() const |
| { |
| // 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 hasAttributeWithoutSynchronization(multipleAttr); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setSize(unsigned size) |
| { |
| if (!size) |
| return Exception { IndexSizeError }; |
| setUnsignedIntegralAttribute(sizeAttr, limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(size, defaultSize)); |
| return { }; |
| } |
| |
| URL HTMLInputElement::src() const |
| { |
| return document().completeURL(attributeWithoutSynchronization(srcAttr)); |
| } |
| |
| void HTMLInputElement::setAutoFilled(bool autoFilled) |
| { |
| if (autoFilled == m_isAutoFilled) |
| return; |
| |
| m_isAutoFilled = autoFilled; |
| invalidateStyleForSubtree(); |
| } |
| |
| void HTMLInputElement::setAutoFilledAndViewable(bool autoFilledAndViewable) |
| { |
| if (autoFilledAndViewable == m_isAutoFilledAndViewable) |
| return; |
| |
| m_isAutoFilledAndViewable = autoFilledAndViewable; |
| invalidateStyleForSubtree(); |
| } |
| |
| void HTMLInputElement::setAutoFilledAndObscured(bool autoFilledAndObscured) |
| { |
| if (autoFilledAndObscured == m_isAutoFilledAndObscured) |
| return; |
| |
| m_isAutoFilledAndObscured = autoFilledAndObscured; |
| invalidateStyleForSubtree(); |
| } |
| |
| void HTMLInputElement::setShowAutoFillButton(AutoFillButtonType autoFillButtonType) |
| { |
| if (static_cast<uint8_t>(autoFillButtonType) == m_autoFillButtonType) |
| return; |
| |
| m_lastAutoFillButtonType = m_autoFillButtonType; |
| m_autoFillButtonType = static_cast<uint8_t>(autoFillButtonType); |
| m_inputType->updateAutoFillButton(); |
| updateInnerTextElementEditability(); |
| invalidateStyleForSubtree(); |
| } |
| |
| FileList* HTMLInputElement::files() |
| { |
| return m_inputType->files(); |
| } |
| |
| void HTMLInputElement::setFiles(RefPtr<FileList>&& files, WasSetByJavaScript wasSetByJavaScript) |
| { |
| m_inputType->setFiles(WTFMove(files), wasSetByJavaScript); |
| } |
| |
| #if ENABLE(DRAG_SUPPORT) |
| bool HTMLInputElement::receiveDroppedFiles(const DragData& dragData) |
| { |
| return m_inputType->receiveDroppedFiles(dragData); |
| } |
| #endif |
| |
| Icon* HTMLInputElement::icon() const |
| { |
| return m_inputType->icon(); |
| } |
| |
| String HTMLInputElement::displayString() const |
| { |
| return m_inputType->displayString(); |
| } |
| |
| bool HTMLInputElement::canReceiveDroppedFiles() const |
| { |
| return m_canReceiveDroppedFiles; |
| } |
| |
| void HTMLInputElement::setCanReceiveDroppedFiles(bool canReceiveDroppedFiles) |
| { |
| if (m_canReceiveDroppedFiles == canReceiveDroppedFiles) |
| return; |
| m_canReceiveDroppedFiles = canReceiveDroppedFiles; |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| String HTMLInputElement::visibleValue() const |
| { |
| return m_inputType->visibleValue(); |
| } |
| |
| 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::needsSuspensionCallback() |
| { |
| if (m_inputType->shouldResetOnDocumentActivation()) |
| return true; |
| |
| // Sensitive input elements are marked with autocomplete=off, and we want to wipe them out |
| // when going back; returning true here arranges for us to call reset at the time |
| // the page is restored. Non-empty textual default values indicate that the field |
| // is not really sensitive -- there's no default value for an account number -- |
| // and we would see unexpected results if we reset to something other than blank. |
| bool isSensitive = m_autocomplete == Off && !(m_inputType->isTextType() && !defaultValue().isEmpty()); |
| |
| return isSensitive; |
| } |
| |
| void HTMLInputElement::registerForSuspensionCallbackIfNeeded() |
| { |
| if (needsSuspensionCallback()) |
| document().registerForDocumentSuspensionCallbacks(*this); |
| } |
| |
| void HTMLInputElement::unregisterForSuspensionCallbackIfNeeded() |
| { |
| if (!needsSuspensionCallback()) |
| document().unregisterForDocumentSuspensionCallbacks(*this); |
| } |
| |
| bool HTMLInputElement::isRequiredFormControl() const |
| { |
| return m_inputType->supportsRequired() && isRequired(); |
| } |
| |
| bool HTMLInputElement::matchesReadWritePseudoClass() const |
| { |
| return m_inputType->supportsReadOnly() && !isDisabledOrReadOnly(); |
| } |
| |
| void HTMLInputElement::addSearchResult() |
| { |
| m_inputType->addSearchResult(); |
| } |
| |
| void HTMLInputElement::onSearch() |
| { |
| // 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) |
| downcast<SearchInputType>(*m_inputType.get()).stopSearchEventTimer(); |
| dispatchEvent(Event::create(eventNames().searchEvent, Event::CanBubble::Yes, Event::IsCancelable::No)); |
| } |
| |
| void HTMLInputElement::resumeFromDocumentSuspension() |
| { |
| ASSERT(needsSuspensionCallback()); |
| |
| #if ENABLE(INPUT_TYPE_COLOR) |
| // <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; |
| #endif // ENABLE(INPUT_TYPE_COLOR) |
| document().postTask([inputElement = Ref { *this }] (ScriptExecutionContext&) { |
| inputElement->reset(); |
| }); |
| } |
| |
| #if ENABLE(INPUT_TYPE_COLOR) |
| void HTMLInputElement::prepareForDocumentSuspension() |
| { |
| if (!isColorControl()) |
| return; |
| m_inputType->detach(); |
| } |
| #endif // ENABLE(INPUT_TYPE_COLOR) |
| |
| |
| void HTMLInputElement::willChangeForm() |
| { |
| removeFromRadioButtonGroup(); |
| HTMLTextFormControlElement::willChangeForm(); |
| } |
| |
| void HTMLInputElement::didChangeForm() |
| { |
| addToRadioButtonGroup(); |
| HTMLTextFormControlElement::didChangeForm(); |
| } |
| |
| Node::InsertedIntoAncestorResult HTMLInputElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| if (isRadioButton()) |
| updateValidity(); |
| HTMLTextFormControlElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| #if ENABLE(DATALIST_ELEMENT) |
| resetListAttributeTargetObserver(); |
| #endif |
| return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
| } |
| |
| void HTMLInputElement::didFinishInsertingNode() |
| { |
| HTMLTextFormControlElement::didFinishInsertingNode(); |
| if (isInTreeScope() && !form()) |
| addToRadioButtonGroup(); |
| #if ENABLE(DATALIST_ELEMENT) |
| if (isConnected() && m_hasNonEmptyList) |
| dataListMayHaveChanged(); |
| #endif |
| } |
| |
| void HTMLInputElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| if (removalType.treeScopeChanged && isRadioButton()) |
| oldParentOfRemovedTree.treeScope().radioButtonGroups().removeButton(*this); |
| if (removalType.disconnectedFromDocument && !form()) |
| removeFromRadioButtonGroup(); |
| HTMLTextFormControlElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| ASSERT(!isConnected()); |
| if (removalType.disconnectedFromDocument && !form() && isRadioButton()) |
| updateValidity(); |
| #if ENABLE(DATALIST_ELEMENT) |
| resetListAttributeTargetObserver(); |
| #endif |
| } |
| |
| void HTMLInputElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
| { |
| if (imageLoader()) |
| imageLoader()->elementDidMoveToNewDocument(oldDocument); |
| |
| // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered |
| if (needsSuspensionCallback()) { |
| oldDocument.unregisterForDocumentSuspensionCallbacks(*this); |
| newDocument.registerForDocumentSuspensionCallbacks(*this); |
| } |
| |
| #if ENABLE(TOUCH_EVENTS) |
| if (m_hasTouchEventHandler) { |
| oldDocument.didRemoveEventTargetNode(*this); |
| newDocument.didAddTouchEventHandler(*this); |
| } |
| #endif |
| |
| HTMLTextFormControlElement::didMoveToNewDocument(oldDocument, newDocument); |
| } |
| |
| void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const |
| { |
| HTMLTextFormControlElement::addSubresourceAttributeURLs(urls); |
| |
| addSubresourceURL(urls, src()); |
| } |
| |
| bool HTMLInputElement::computeWillValidate() const |
| { |
| return m_inputType->supportsValidation() && HTMLTextFormControlElement::computeWillValidate(); |
| } |
| |
| void HTMLInputElement::requiredStateChanged() |
| { |
| HTMLTextFormControlElement::requiredStateChanged(); |
| if (auto* buttons = radioButtonGroups()) |
| buttons->requiredStateChanged(*this); |
| m_inputType->requiredStateChanged(); |
| } |
| |
| Color HTMLInputElement::valueAsColor() const |
| { |
| return m_inputType->valueAsColor(); |
| } |
| |
| void HTMLInputElement::selectColor(StringView color) |
| { |
| m_inputType->selectColor(color); |
| } |
| |
| Vector<Color> HTMLInputElement::suggestedColors() const |
| { |
| return m_inputType->suggestedColors(); |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| |
| RefPtr<HTMLElement> HTMLInputElement::list() const |
| { |
| return dataList(); |
| } |
| |
| RefPtr<HTMLDataListElement> HTMLInputElement::dataList() const |
| { |
| if (!m_hasNonEmptyList) |
| return nullptr; |
| |
| if (!m_inputType->shouldRespectListAttribute()) |
| return nullptr; |
| |
| RefPtr<Element> element = treeScope().getElementById(attributeWithoutSynchronization(listAttr)); |
| if (!is<HTMLDataListElement>(element)) |
| return nullptr; |
| |
| return downcast<HTMLDataListElement>(element.get()); |
| } |
| |
| void HTMLInputElement::resetListAttributeTargetObserver() |
| { |
| if (isConnected()) { |
| if (auto& listAttrValue = attributeWithoutSynchronization(listAttr); !listAttrValue.isNull()) { |
| m_listAttributeTargetObserver = makeUnique<ListAttributeTargetObserver>(listAttrValue, this); |
| return; |
| } |
| } |
| |
| m_listAttributeTargetObserver = nullptr; |
| } |
| |
| void HTMLInputElement::dataListMayHaveChanged() |
| { |
| m_inputType->dataListMayHaveChanged(); |
| } |
| |
| bool HTMLInputElement::isFocusingWithDataListDropdown() const |
| { |
| return m_inputType->isFocusingWithDataListDropdown(); |
| } |
| |
| #endif // ENABLE(DATALIST_ELEMENT) |
| |
| bool HTMLInputElement::isPresentingAttachedView() const |
| { |
| return m_inputType->isPresentingAttachedView(); |
| } |
| |
| bool HTMLInputElement::isSteppable() const |
| { |
| return m_inputType->isSteppable(); |
| } |
| |
| DateComponentsType HTMLInputElement::dateType() const |
| { |
| return m_inputType->dateType(); |
| } |
| |
| bool HTMLInputElement::isTextButton() const |
| { |
| return m_inputType->isTextButton(); |
| } |
| |
| bool HTMLInputElement::isRadioButton() const |
| { |
| return m_inputType->isRadioButton(); |
| } |
| |
| bool HTMLInputElement::isSearchField() const |
| { |
| return m_inputType->isSearchField(); |
| } |
| |
| bool HTMLInputElement::isInputTypeHidden() const |
| { |
| return m_inputType->isHiddenType(); |
| } |
| |
| bool HTMLInputElement::isPasswordField() const |
| { |
| return m_inputType->isPasswordField(); |
| } |
| |
| bool HTMLInputElement::isCheckbox() const |
| { |
| return m_inputType->isCheckbox(); |
| } |
| |
| bool HTMLInputElement::isRangeControl() const |
| { |
| return m_inputType->isRangeControl(); |
| } |
| |
| #if ENABLE(INPUT_TYPE_COLOR) |
| bool HTMLInputElement::isColorControl() const |
| { |
| return m_inputType->isColorControl(); |
| } |
| #endif |
| |
| bool HTMLInputElement::isText() const |
| { |
| return m_inputType->isTextType(); |
| } |
| |
| bool HTMLInputElement::isEmailField() const |
| { |
| return m_inputType->isEmailField(); |
| } |
| |
| bool HTMLInputElement::isFileUpload() const |
| { |
| return m_inputType->isFileUpload(); |
| } |
| |
| bool HTMLInputElement::isImageButton() const |
| { |
| return m_inputType->isImageButton(); |
| } |
| |
| bool HTMLInputElement::isNumberField() const |
| { |
| return m_inputType->isNumberField(); |
| } |
| |
| bool HTMLInputElement::isSubmitButton() const |
| { |
| return m_inputType->isSubmitButton(); |
| } |
| |
| bool HTMLInputElement::isTelephoneField() const |
| { |
| return m_inputType->isTelephoneField(); |
| } |
| |
| bool HTMLInputElement::isURLField() const |
| { |
| return m_inputType->isURLField(); |
| } |
| |
| bool HTMLInputElement::isDateField() const |
| { |
| return m_inputType->isDateField(); |
| } |
| |
| bool HTMLInputElement::isDateTimeLocalField() const |
| { |
| return m_inputType->isDateTimeLocalField(); |
| } |
| |
| bool HTMLInputElement::isMonthField() const |
| { |
| return m_inputType->isMonthField(); |
| } |
| |
| bool HTMLInputElement::isTimeField() const |
| { |
| return m_inputType->isTimeField(); |
| } |
| |
| bool HTMLInputElement::isWeekField() const |
| { |
| return m_inputType->isWeekField(); |
| } |
| |
| bool HTMLInputElement::isEnumeratable() const |
| { |
| return m_inputType->isEnumeratable(); |
| } |
| |
| bool HTMLInputElement::supportLabels() const |
| { |
| return m_inputType->supportLabels(); |
| } |
| |
| bool HTMLInputElement::shouldAppearChecked() const |
| { |
| return checked() && m_inputType->isCheckable(); |
| } |
| |
| bool HTMLInputElement::supportsPlaceholder() const |
| { |
| return m_inputType->supportsPlaceholder(); |
| } |
| |
| void HTMLInputElement::updatePlaceholderText() |
| { |
| return m_inputType->updatePlaceholderText(); |
| } |
| |
| bool HTMLInputElement::isEmptyValue() const |
| { |
| return m_inputType->isEmptyValue(); |
| } |
| |
| void HTMLInputElement::maxLengthAttributeChanged(const AtomString& newValue) |
| { |
| unsigned oldEffectiveMaxLength = effectiveMaxLength(); |
| internalSetMaxLength(parseHTMLNonNegativeInteger(newValue).value_or(-1)); |
| if (oldEffectiveMaxLength != effectiveMaxLength()) |
| updateValueIfNeeded(); |
| |
| // FIXME: Do we really need to do this if the effective maxLength has not changed? |
| invalidateStyleForSubtree(); |
| updateValidity(); |
| } |
| |
| void HTMLInputElement::minLengthAttributeChanged(const AtomString& 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() |
| { |
| String newValue = sanitizeValue(m_valueIfDirty); |
| ASSERT(!m_valueIfDirty.isNull() || newValue.isNull()); |
| if (newValue != m_valueIfDirty) |
| setValue(newValue); |
| } |
| |
| String HTMLInputElement::defaultToolTip() const |
| { |
| return m_inputType->defaultToolTip(); |
| } |
| |
| bool HTMLInputElement::matchesIndeterminatePseudoClass() const |
| { |
| // 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(); |
| } |
| |
| bool HTMLInputElement::shouldAppearIndeterminate() const |
| { |
| return m_inputType->shouldAppearIndeterminate(); |
| } |
| |
| #if ENABLE(MEDIA_CAPTURE) |
| MediaCaptureType HTMLInputElement::mediaCaptureType() const |
| { |
| 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 (RadioButtonGroups* buttons = radioButtonGroups()) |
| return buttons->isInRequiredGroup(*this); |
| return false; |
| } |
| |
| Vector<Ref<HTMLInputElement>> HTMLInputElement::radioButtonGroup() const |
| { |
| RadioButtonGroups* buttons = radioButtonGroups(); |
| if (!buttons) |
| return { }; |
| return buttons->groupMembers(*this); |
| } |
| |
| RefPtr<HTMLInputElement> HTMLInputElement::checkedRadioButtonForGroup() const |
| { |
| if (checked()) |
| return const_cast<HTMLInputElement*>(this); |
| |
| auto& name = this->name(); |
| if (RadioButtonGroups* buttons = radioButtonGroups()) |
| return buttons->checkedButtonForGroup(name); |
| |
| if (name.isEmpty()) |
| return nullptr; |
| |
| // The input is not managed by a RadioButtonGroups, we'll need to traverse the tree. |
| for (auto& descendant : descendantsOfType<HTMLInputElement>(rootNode())) { |
| if (descendant.checked() && descendant.isRadioButton() && !descendant.form() && name == descendant.name()) |
| return &descendant; |
| } |
| return nullptr; |
| } |
| |
| RadioButtonGroups* HTMLInputElement::radioButtonGroups() const |
| { |
| if (!isRadioButton()) |
| return nullptr; |
| if (auto* formElement = form()) |
| return &formElement->radioButtonGroups(); |
| if (isInTreeScope()) |
| return &treeScope().radioButtonGroups(); |
| return nullptr; |
| } |
| |
| inline void HTMLInputElement::addToRadioButtonGroup() |
| { |
| if (auto* buttons = radioButtonGroups()) |
| buttons->addButton(*this); |
| } |
| |
| inline void HTMLInputElement::removeFromRadioButtonGroup() |
| { |
| if (auto* buttons = radioButtonGroups()) |
| buttons->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); |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| ListAttributeTargetObserver::ListAttributeTargetObserver(const AtomString& id, HTMLInputElement* element) |
| : IdTargetObserver(element->treeScope().idTargetObserverRegistry(), id) |
| , m_element(element) |
| { |
| } |
| |
| void ListAttributeTargetObserver::idTargetChanged() |
| { |
| m_element->dataListMayHaveChanged(); |
| } |
| #endif |
| |
| ExceptionOr<void> HTMLInputElement::setRangeText(const String& replacement) |
| { |
| if (!m_inputType->supportsSelectionAPI()) |
| return Exception { InvalidStateError }; |
| |
| return HTMLTextFormControlElement::setRangeText(replacement); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode) |
| { |
| if (!m_inputType->supportsSelectionAPI()) |
| return Exception { InvalidStateError }; |
| |
| return HTMLTextFormControlElement::setRangeText(replacement, start, end, selectionMode); |
| } |
| |
| bool HTMLInputElement::shouldTruncateText(const RenderStyle& style) const |
| { |
| if (!isTextField()) |
| return false; |
| return document().focusedElement() != this && style.textOverflow() == TextOverflow::Ellipsis; |
| } |
| |
| void HTMLInputElement::invalidateStyleOnFocusChangeIfNeeded() |
| { |
| if (!isTextField()) |
| return; |
| // Focus change may affect the result of shouldTruncateText(). |
| if (auto* style = renderStyle(); style && style->textOverflow() == TextOverflow::Ellipsis) |
| invalidateStyleForSubtreeInternal(); |
| } |
| |
| 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 { }; |
| } |
| |
| 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 { }; |
| } |
| |
| static Ref<CSSLinearGradientValue> autoFillStrongPasswordMaskImage() |
| { |
| CSSGradientColorStopList stops { |
| { CSSValuePool::singleton().createColorValue(Color::black), CSSValuePool::singleton().createValue(50, CSSUnitType::CSS_PERCENTAGE), { } }, |
| { CSSValuePool::singleton().createColorValue(Color::transparentBlack), CSSValuePool::singleton().createValue(100, CSSUnitType::CSS_PERCENTAGE), { } } |
| }; |
| |
| auto colorInterpolationMethod = CSSGradientColorInterpolationMethod::legacyMethod(AlphaPremultiplication::Unpremultiplied); |
| auto gradient = CSSLinearGradientValue::create(CSSGradientRepeat::NonRepeating, CSSGradientType::CSSLinearGradient, colorInterpolationMethod, WTFMove(stops)); |
| gradient->setAngle(CSSValuePool::singleton().createValue(90, CSSUnitType::CSS_DEG)); |
| gradient->resolveRGBColors(); |
| return gradient; |
| } |
| |
| RenderStyle HTMLInputElement::createInnerTextStyle(const RenderStyle& style) |
| { |
| auto textBlockStyle = RenderStyle::create(); |
| textBlockStyle.inheritFrom(style); |
| adjustInnerTextStyle(style, textBlockStyle); |
| |
| textBlockStyle.setWhiteSpace(WhiteSpace::Pre); |
| textBlockStyle.setOverflowWrap(OverflowWrap::Normal); |
| textBlockStyle.setOverflowX(Overflow::Hidden); |
| textBlockStyle.setOverflowY(Overflow::Hidden); |
| textBlockStyle.setTextOverflow(shouldTruncateText(style) ? TextOverflow::Ellipsis : TextOverflow::Clip); |
| |
| textBlockStyle.setDisplay(DisplayType::Block); |
| |
| if (hasAutoFillStrongPasswordButton() && !isDisabledOrReadOnly()) { |
| textBlockStyle.setDisplay(DisplayType::InlineBlock); |
| textBlockStyle.setMaxWidth(Length { 100, LengthType::Percent }); |
| textBlockStyle.setColor(Color::black.colorWithAlphaByte(153)); |
| textBlockStyle.setTextOverflow(TextOverflow::Clip); |
| textBlockStyle.setMaskImage(StyleGeneratedImage::create(autoFillStrongPasswordMaskImage())); |
| // A stacking context is needed for the mask. |
| if (textBlockStyle.hasAutoUsedZIndex()) |
| textBlockStyle.setUsedZIndex(0); |
| } |
| |
| // Do not allow line-height to be smaller than our default. |
| if (textBlockStyle.fontMetrics().lineSpacing() > style.computedLineHeight()) |
| textBlockStyle.setLineHeight(RenderStyle::initialLineHeight()); |
| |
| return textBlockStyle; |
| } |
| |
| void HTMLInputElement::capsLockStateMayHaveChanged() |
| { |
| m_inputType->capsLockStateMayHaveChanged(); |
| } |
| |
| String HTMLInputElement::resultForDialogSubmit() const |
| { |
| return m_inputType->resultForDialogSubmit(); |
| } |
| |
| String HTMLInputElement::placeholder() const |
| { |
| // According to the HTML5 specification, we need to remove CR and LF from |
| // the attribute value. |
| String attributeValue = attributeWithoutSynchronization(placeholderAttr); |
| return attributeValue.removeCharacters([](UChar c) { |
| return c == newlineCharacter || c == carriageReturn; |
| }); |
| } |
| |
| } // namespace |