| /* |
| * 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 "ColorInputType.h" |
| #include "DOMWindow.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 "RadioInputType.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 |
| |
| static constexpr int maxSavedResults = 256; |
| |
| HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) |
| : HTMLTextFormControlElement(tagName, document, form) |
| , m_parsingInProgress(createdByParser) |
| // m_inputType is lazily created when constructed by the parser to avoid constructing unnecessarily a text inputType, |
| // just to destroy them when the |type| attribute gets set by the parser to something else than 'text'. |
| , m_inputType(createdByParser ? nullptr : RefPtr { InputType::createText(*this) }) |
| { |
| ASSERT(hasTagName(inputTag)); |
| setHasCustomStyleResolveCallbacks(); |
| } |
| |
| Ref<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) |
| { |
| return adoptRef(*new HTMLInputElement(tagName, document, form, createdByParser)); |
| } |
| |
| HTMLImageLoader& HTMLInputElement::ensureImageLoader() |
| { |
| if (!m_imageLoader) |
| m_imageLoader = makeUnique<HTMLImageLoader>(*this); |
| return *m_imageLoader; |
| } |
| |
| 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(); |
| } |
| |
| RefPtr<TextControlInnerTextElement> HTMLInputElement::innerTextElementCreatingShadowSubtreeIfNeeded() |
| { |
| return m_inputType->innerTextElementCreatingShadowSubtreeIfNeeded(); |
| } |
| |
| 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->isValidValue(value)) |
| return false; |
| |
| return !tooShort(value, IgnoreDirtyFlag) && !tooLong(value, IgnoreDirtyFlag); |
| } |
| |
| 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()); |
| unsigned start = 0; |
| auto direction = SelectionHasNoDirection; |
| auto* frame = document().frame(); |
| if (frame && frame->editor().behavior().shouldMoveSelectionToEndWhenFocusingTextInput()) { |
| start = std::numeric_limits<unsigned>::max(); |
| direction = SelectionHasForwardDirection; |
| } |
| unsigned end = restorationMode == SelectionRestorationMode::PlaceCaretAtStart ? start : std::numeric_limits<unsigned>::max(); |
| setSelectionRange(start, end, direction, revealMode, Element::defaultFocusTextStateChangeIntent()); |
| } |
| |
| void HTMLInputElement::endEditing() |
| { |
| if (!isTextField()) |
| return; |
| |
| if (RefPtr 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, AtomString { std::exchange(m_valueIfDirty, { }) }); |
| |
| m_inputType->destroyShadowSubtree(); |
| m_inputType->detachFromElement(); |
| |
| bool previouslySelectable = m_inputType->supportsSelectionAPI(); |
| |
| m_inputType = WTFMove(newType); |
| m_inputType->createShadowSubtreeIfNeeded(); |
| |
| 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 (auto* form = this->form(); form && wasSuccessfulSubmitButtonCandidate != m_inputType->canBeSuccessfulSubmitButton()) |
| form->resetDefaultButton(); |
| |
| runPostTypeUpdateTasks(); |
| |
| // https://html.spec.whatwg.org/multipage/input.html#input-type-change |
| // 8. Let nowSelectable be true if setRangeText() now applies to the element, and false otherwise. |
| bool nowSelectable = m_inputType->supportsSelectionAPI(); |
| // 9. If previouslySelectable is false and nowSelectable is true, set the element's text entry cursor position to the beginning of the text control, and set its selection direction to "none". |
| if (!previouslySelectable && nowSelectable) { |
| TextFieldSelectionDirection direction = SelectionHasNoDirection; |
| // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-direction |
| RefPtr frame = document().frame(); |
| if (isTextField() && frame && frame->editor().behavior().shouldConsiderSelectionAsDirectional()) |
| direction = SelectionHasForwardDirection; |
| cacheSelection(0, 0, direction); |
| } |
| |
| updateValidity(); |
| } |
| |
| 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(); |
| } |
| |
| 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) |
| { |
| return Ref { *m_inputType }->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); |
| |
| auto& type = attributeWithoutSynchronization(typeAttr); |
| if (type.isNull()) { |
| m_inputType = InputType::createText(*this); |
| updateWillValidateAndValidity(); |
| return; |
| } |
| |
| m_hasType = true; |
| m_inputType = InputType::create(*this, type); |
| updateWillValidateAndValidity(); |
| registerForSuspensionCallbackIfNeeded(); |
| runPostTypeUpdateTasks(); |
| updateValidity(); |
| } |
| |
| void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomString& value) |
| { |
| ASSERT(m_inputType); |
| Ref protectedInputType { *m_inputType }; |
| |
| if (name == nameAttr) { |
| removeFromRadioButtonGroup(); |
| m_name = value; |
| addToRadioButtonGroup(); |
| HTMLTextFormControlElement::parseAttribute(name, value); |
| } else if (name == autocompleteAttr) { |
| if (equalLettersIgnoringASCIICase(value, "off"_s)) { |
| 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) { |
| if (hasAttributeWithoutSynchronization(checkedAttr)) |
| setChecked(true); |
| 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 protectedInputType { *m_inputType }; |
| return m_inputType->isFormDataAppendable() && m_inputType->appendFormData(formData); |
| } |
| |
| void HTMLInputElement::reset() |
| { |
| if (m_inputType->storesValueSeparateFromAttribute()) |
| setValue({ }); |
| |
| 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) |
| { |
| m_dirtyCheckednessFlag = true; |
| if (checked() == isChecked) |
| return; |
| |
| m_inputType->willUpdateCheckedness(isChecked); |
| |
| Style::PseudoClassChangeInvalidation checkedInvalidation(*this, CSSSelector::PseudoClassChecked, isChecked); |
| |
| m_isChecked = isChecked; |
| |
| if (auto* buttons = radioButtonGroups()) |
| buttons->updateCheckedState(*this); |
| if (auto* renderer = this->renderer(); 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 (auto* renderer = this->renderer()) { |
| if (auto* cache = renderer->document().existingAXObjectCache()) |
| cache->checkedStateChanged(this); |
| } |
| } |
| |
| void HTMLInputElement::setIndeterminate(bool newValue) |
| { |
| if (indeterminate() == newValue) |
| return; |
| |
| Style::PseudoClassChangeInvalidation indeterminateInvalidation(*this, CSSSelector::PseudoClassIndeterminate, newValue); |
| m_isIndeterminate = newValue; |
| |
| if (auto* renderer = this->renderer(); renderer && renderer->style().hasEffectiveAppearance()) |
| renderer->theme().stateChanged(*renderer, ControlStates::States::Checked); |
| } |
| |
| 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 |
| { |
| if (auto* fileInput = dynamicDowncast<FileInputType>(*m_inputType)) |
| return fileInput->firstElementPathForInputValue(); |
| |
| if (!m_valueIfDirty.isNull()) |
| return m_valueIfDirty; |
| |
| if (auto& valueString = attributeWithoutSynchronization(valueAttr); !valueString.isNull()) { |
| if (auto sanitizedValue = sanitizeValue(valueString); !sanitizedValue.isNull()) |
| return sanitizedValue; |
| } |
| |
| return m_inputType->fallbackValue(); |
| } |
| |
| String HTMLInputElement::valueWithDefault() const |
| { |
| if (auto value = this->value(); !value.isNull()) |
| return value; |
| |
| return m_inputType->defaultValue(); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setValue(const String& value, TextFieldEventBehavior eventBehavior, TextControlSetValueSelection selection) |
| { |
| if (isFileUpload() && !value.isEmpty()) |
| return Exception { InvalidStateError }; |
| |
| Ref protectedThis { *this }; |
| EventQueueScope scope; |
| auto sanitizedValue = sanitizeValue(value); |
| bool valueChanged = sanitizedValue != this->value(); |
| |
| setLastChangeWasNotUserEdit(); |
| setFormControlValueMatchesRenderer(false); |
| m_inputType->setValue(WTFMove(sanitizedValue), valueChanged, eventBehavior, selection); |
| |
| bool wasModifiedProgrammatically = eventBehavior == DispatchNoEvent; |
| if (wasModifiedProgrammatically) { |
| resignStrongPasswordAppearance(); |
| |
| if (m_isAutoFilledAndObscured) |
| setAutoFilledAndObscured(false); |
| } |
| |
| 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"_s ? 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 (!value.isEmpty()) |
| return; |
| |
| if (m_isAutoFilledAndViewable) |
| setAutoFilledAndViewable(false); |
| |
| if (m_isAutoFilledAndObscured) |
| setAutoFilledAndObscured(false); |
| } |
| |
| void HTMLInputElement::willDispatchEvent(Event& event, InputElementClickState& state) |
| { |
| auto& eventNames = WebCore::eventNames(); |
| 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 (auto* mouseEvent = dynamicDowncast<MouseEvent>(event); mouseEvent && mouseEvent->type() == eventNames().clickEvent && mouseEvent->button() == LeftButton) { |
| m_inputType->handleClickEvent(*mouseEvent); |
| if (mouseEvent->defaultHandled()) |
| return; |
| } |
| |
| #if ENABLE(TOUCH_EVENTS) |
| if (auto* touchEvent = dynamicDowncast<TouchEvent>(event)) { |
| m_inputType->handleTouchEvent(*touchEvent); |
| if (event.defaultHandled()) |
| return; |
| } |
| #endif |
| |
| if (auto* keyboardEvent = dynamicDowncast<KeyboardEvent>(event); keyboardEvent && keyboardEvent->type() == eventNames().keydownEvent) { |
| auto shouldCallBaseEventHandler = m_inputType->handleKeydownEvent(*keyboardEvent); |
| 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 (auto* keyboardEvent = dynamicDowncast<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 (auto* beforeTextInsertedEvent = dynamicDowncast<BeforeTextInsertedEvent>(event); beforeTextInsertedEvent) |
| m_inputType->handleBeforeTextInsertedEvent(*beforeTextInsertedEvent); |
| |
| if (auto* mouseEvent = dynamicDowncast<MouseEvent>(event); mouseEvent && mouseEvent->type() == eventNames().mousedownEvent) { |
| m_inputType->handleMouseDownEvent(*mouseEvent); |
| if (mouseEvent->defaultHandled()) |
| return; |
| } |
| |
| m_inputType->forwardEvent(event); |
| |
| if (!callBaseClassEarly && !event.defaultHandled()) |
| HTMLTextFormControlElement::defaultEventHandler(event); |
| } |
| |
| bool HTMLInputElement::willRespondToMouseClickEventsWithEditability(Editability editability) const |
| { |
| if (!isDisabledFormControl()) |
| return true; |
| |
| return HTMLTextFormControlElement::willRespondToMouseClickEventsWithEditability(editability); |
| } |
| |
| bool HTMLInputElement::isURLAttribute(const Attribute& attribute) const |
| { |
| return attribute.name() == srcAttr || attribute.name() == formactionAttr || HTMLTextFormControlElement::isURLAttribute(attribute); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::showPicker() |
| { |
| auto* frame = document().frame(); |
| if (!frame) |
| return { }; |
| |
| if (isDisabledOrReadOnly()) |
| return Exception { InvalidStateError, "Input showPicker() cannot be used on immutable controls."_s }; |
| |
| // In cross-origin iframes it should throw a "SecurityError" DOMException except on file and color. In same-origin iframes it should work fine. |
| // https://github.com/whatwg/html/issues/6909#issuecomment-917138991 |
| if (!m_inputType->allowsShowPickerAcrossFrames()) { |
| Frame& topFrame = frame->tree().top(); |
| if (!frame->document()->securityOrigin().isSameOriginAs(topFrame.document()->securityOrigin())) |
| return Exception { SecurityError, "Input showPicker() called from cross-origin iframe."_s }; |
| } |
| |
| auto* window = frame->window(); |
| if (!window || !window->hasTransientActivation()) |
| return Exception { NotAllowedError, "Input showPicker() requires a user gesture."_s }; |
| |
| m_inputType->showPicker(); |
| return { }; |
| } |
| |
| const AtomString& HTMLInputElement::defaultValue() const |
| { |
| return attributeWithoutSynchronization(valueAttr); |
| } |
| |
| void HTMLInputElement::setDefaultValue(const AtomString& 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(StringView 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(StringView type) |
| { |
| if (type.length() < 2) |
| return false; |
| return type[0] == '.'; |
| } |
| |
| static Vector<String> parseAcceptAttribute(StringView acceptString, bool (*predicate)(StringView)) |
| { |
| Vector<String> types; |
| if (acceptString.isEmpty()) |
| return types; |
| |
| for (auto splitType : acceptString.split(',')) { |
| auto trimmedType = splitType.stripLeadingAndTrailingMatchedCharacters(isHTMLSpace<UChar>); |
| if (trimmedType.isEmpty()) |
| continue; |
| if (!predicate(trimmedType)) |
| continue; |
| types.append(trimmedType.convertToASCIILowercase()); |
| } |
| |
| return types; |
| } |
| |
| Vector<String> HTMLInputElement::acceptMIMETypes() const |
| { |
| return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidMIMEType); |
| } |
| |
| Vector<String> HTMLInputElement::acceptFileExtensions() const |
| { |
| 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; |
| |
| Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassAutofill, autoFilled); |
| m_isAutoFilled = autoFilled; |
| } |
| |
| void HTMLInputElement::setAutoFilledAndViewable(bool autoFilledAndViewable) |
| { |
| if (autoFilledAndViewable == m_isAutoFilledAndViewable) |
| return; |
| |
| Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassAutofillStrongPasswordViewable, autoFilledAndViewable); |
| m_isAutoFilledAndViewable = autoFilledAndViewable; |
| } |
| |
| void HTMLInputElement::setAutoFilledAndObscured(bool autoFilledAndObscured) |
| { |
| if (autoFilledAndObscured == m_isAutoFilledAndObscured) |
| return; |
| |
| Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassAutofillAndObscured, autoFilledAndObscured); |
| m_isAutoFilledAndObscured = autoFilledAndObscured; |
| } |
| |
| void HTMLInputElement::setShowAutoFillButton(AutoFillButtonType autoFillButtonType) |
| { |
| if (autoFillButtonType == this->autoFillButtonType()) |
| return; |
| |
| m_lastAutoFillButtonType = m_autoFillButtonType; |
| m_autoFillButtonType = static_cast<uint8_t>(autoFillButtonType); |
| m_inputType->updateAutoFillButton(); |
| updateInnerTextElementEditability(); |
| invalidateStyleForSubtree(); |
| } |
| |
| FileList* HTMLInputElement::files() |
| { |
| if (auto* fileInputType = dynamicDowncast<FileInputType>(*m_inputType)) |
| return &fileInputType->files(); |
| return nullptr; |
| } |
| |
| void HTMLInputElement::setFiles(RefPtr<FileList>&& files, WasSetByJavaScript wasSetByJavaScript) |
| { |
| if (auto* fileInputType = dynamicDowncast<FileInputType>(*m_inputType)) |
| fileInputType->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(); |
| } |
| |
| 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. |
| auto* searchInputType = dynamicDowncast<SearchInputType>(*m_inputType); |
| if (!searchInputType) |
| return; |
| |
| searchInputType->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() |
| { |
| m_inputType->detach(); |
| } |
| #endif // ENABLE(INPUT_TYPE_COLOR) |
| |
| void HTMLInputElement::willChangeForm() |
| { |
| removeFromRadioButtonGroup(); |
| HTMLTextFormControlElement::willChangeForm(); |
| } |
| |
| void HTMLInputElement::didChangeForm() |
| { |
| HTMLTextFormControlElement::didChangeForm(); |
| addToRadioButtonGroup(); |
| } |
| |
| Node::InsertedIntoAncestorResult HTMLInputElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| HTMLTextFormControlElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| #if ENABLE(DATALIST_ELEMENT) |
| resetListAttributeTargetObserver(); |
| #endif |
| if (isRadioButton()) |
| updateValidity(); |
| if (insertionType.connectedToDocument && m_inputType->needsShadowSubtree() && !m_inputType->hasCreatedShadowSubtree() && !m_hasPendingUserAgentShadowTreeUpdate) { |
| document().addElementWithPendingUserAgentShadowTreeUpdate(*this); |
| m_hasPendingUserAgentShadowTreeUpdate = true; |
| } |
| return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
| } |
| |
| void HTMLInputElement::updateUserAgentShadowTree() |
| { |
| ASSERT(m_hasPendingUserAgentShadowTreeUpdate); |
| document().removeElementWithPendingUserAgentShadowTreeUpdate(*this); |
| m_hasPendingUserAgentShadowTreeUpdate = false; |
| m_inputType->createShadowSubtreeIfNeeded(); |
| } |
| |
| void HTMLInputElement::didFinishInsertingNode() |
| { |
| HTMLTextFormControlElement::didFinishInsertingNode(); |
| if (isInTreeScope() && !form()) |
| addToRadioButtonGroup(); |
| } |
| |
| void HTMLInputElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| HTMLTextFormControlElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| if (removalType.treeScopeChanged && isRadioButton()) |
| oldParentOfRemovedTree.treeScope().radioButtonGroups().removeButton(*this); |
| if (removalType.disconnectedFromDocument && !form()) |
| removeFromRadioButtonGroup(); |
| if (removalType.disconnectedFromDocument && m_hasPendingUserAgentShadowTreeUpdate) { |
| document().removeElementWithPendingUserAgentShadowTreeUpdate(*this); |
| m_hasPendingUserAgentShadowTreeUpdate = false; |
| } |
| 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 |
| { |
| #if ENABLE(INPUT_TYPE_COLOR) |
| if (auto* colorInputType = dynamicDowncast<ColorInputType>(*m_inputType)) |
| return colorInputType->valueAsColor(); |
| #endif |
| return Color::transparentBlack; |
| } |
| |
| void HTMLInputElement::selectColor(StringView color) |
| { |
| #if ENABLE(INPUT_TYPE_COLOR) |
| if (auto* colorInputType = dynamicDowncast<ColorInputType>(*m_inputType)) |
| colorInputType->selectColor(color); |
| #else |
| UNUSED_PARAM(color); |
| #endif |
| } |
| |
| Vector<Color> HTMLInputElement::suggestedColors() const |
| { |
| #if ENABLE(INPUT_TYPE_COLOR) |
| if (auto* colorInputType = dynamicDowncast<ColorInputType>(*m_inputType)) |
| return colorInputType->suggestedColors(); |
| #endif |
| return { }; |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| |
| RefPtr<HTMLElement> HTMLInputElement::list() const |
| { |
| return dataList(); |
| } |
| |
| RefPtr<HTMLDataListElement> HTMLInputElement::dataList() const |
| { |
| if (!m_hasNonEmptyList || !m_inputType->shouldRespectListAttribute()) |
| return nullptr; |
| |
| return dynamicDowncast<HTMLDataListElement>(treeScope().getElementById(attributeWithoutSynchronization(listAttr))); |
| } |
| |
| 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(); |
| |
| updateValidity(); |
| } |
| |
| void HTMLInputElement::minLengthAttributeChanged(const AtomString& newValue) |
| { |
| int oldMinLength = minLength(); |
| internalSetMinLength(parseHTMLNonNegativeInteger(newValue).value_or(-1)); |
| if (oldMinLength != minLength()) |
| updateValueIfNeeded(); |
| |
| updateValidity(); |
| } |
| |
| void HTMLInputElement::updateValueIfNeeded() |
| { |
| auto 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"_s)) |
| return MediaCaptureTypeUser; |
| |
| return MediaCaptureTypeEnvironment; |
| } |
| #endif |
| |
| bool HTMLInputElement::isInRequiredRadioButtonGroup() |
| { |
| ASSERT(isRadioButton()); |
| if (auto* buttons = radioButtonGroups()) |
| return buttons->isInRequiredGroup(*this); |
| return false; |
| } |
| |
| Vector<Ref<HTMLInputElement>> HTMLInputElement::radioButtonGroup() const |
| { |
| if (auto* buttons = radioButtonGroups()) |
| return buttons->groupMembers(*this); |
| return { }; |
| } |
| |
| RefPtr<HTMLInputElement> HTMLInputElement::checkedRadioButtonForGroup() const |
| { |
| ASSERT(isRadioButton()); |
| |
| if (checked()) |
| return const_cast<HTMLInputElement*>(this); |
| |
| auto& name = this->name(); |
| if (auto* buttons = radioButtonGroups()) |
| return buttons->checkedButtonForGroup(name); |
| |
| if (name.isEmpty()) |
| return nullptr; |
| |
| ASSERT(!isConnected()); |
| ASSERT(!form()); |
| |
| // The input is not managed by a RadioButtonGroups, we'll need to traverse the tree. |
| RefPtr<HTMLInputElement> checkedRadio; |
| RadioInputType::forEachButtonInDetachedGroup(rootNode(), name, [&](auto& input) { |
| if (input.checked()) { |
| checkedRadio = &input; |
| return false; |
| } |
| return true; |
| }); |
| return checkedRadio; |
| } |
| |
| 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(); |
| } |
| |
| std::optional<unsigned> HTMLInputElement::selectionStartForBindings() const |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return std::nullopt; |
| |
| return selectionStart(); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setSelectionStartForBindings(std::optional<unsigned> start) |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return Exception { InvalidStateError, "The input element's type ('" + m_inputType->formControlType() + "') does not support selection." }; |
| |
| setSelectionStart(start.value_or(0)); |
| return { }; |
| } |
| |
| std::optional<unsigned> HTMLInputElement::selectionEndForBindings() const |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return std::nullopt; |
| |
| return selectionEnd(); |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setSelectionEndForBindings(std::optional<unsigned> end) |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return Exception { InvalidStateError, "The input element's type ('" + m_inputType->formControlType() + "') does not support selection." }; |
| |
| setSelectionEnd(end.value_or(0)); |
| return { }; |
| } |
| |
| ExceptionOr<String> HTMLInputElement::selectionDirectionForBindings() const |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return String(); |
| |
| return String { selectionDirection() }; |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setSelectionDirectionForBindings(const String& direction) |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return Exception { InvalidStateError, "The input element's type ('" + m_inputType->formControlType() + "') does not support selection." }; |
| |
| setSelectionDirection(direction); |
| return { }; |
| } |
| |
| ExceptionOr<void> HTMLInputElement::setSelectionRangeForBindings(unsigned start, unsigned end, const String& direction) |
| { |
| if (!canHaveSelection() || !m_inputType->supportsSelectionAPI()) |
| return Exception { InvalidStateError, "The input element's type ('" + m_inputType->formControlType() + "') does not support selection." }; |
| |
| 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.metricsOfPrimaryFont().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 |