diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:20:33 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:28:57 +0000 |
commit | d17ea114e5ef69ad5d5d7413280a13e6428098aa (patch) | |
tree | 2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/core/html/forms | |
parent | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff) | |
download | qtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz |
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/html/forms')
216 files changed, 41225 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/html/forms/OWNERS b/chromium/third_party/blink/renderer/core/html/forms/OWNERS new file mode 100644 index 00000000000..53439ec8720 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/OWNERS @@ -0,0 +1,5 @@ +keishi@chromium.org +tkent@chromium.org + +# TEAM: dom-dev@chromium.org +# COMPONENT: Blink>Forms diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc new file mode 100644 index 00000000000..83d3d68fd88 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.cc @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h" + +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_button.h" + +namespace blink { + +using namespace HTMLNames; + +BaseButtonInputType::BaseButtonInputType(HTMLInputElement& element) + : InputType(element), KeyboardClickableInputTypeView(element) {} + +void BaseButtonInputType::Trace(blink::Visitor* visitor) { + KeyboardClickableInputTypeView::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* BaseButtonInputType::CreateView() { + return this; +} + +void BaseButtonInputType::CreateShadowSubtree() { + DCHECK(GetElement().UserAgentShadowRoot()); + GetElement().UserAgentShadowRoot()->AppendChild( + Text::Create(GetElement().GetDocument(), DisplayValue())); +} + +void BaseButtonInputType::ValueAttributeChanged() { + ToTextOrDie(GetElement().UserAgentShadowRoot()->firstChild()) + ->setData(DisplayValue()); +} + +String BaseButtonInputType::DisplayValue() const { + return GetElement().ValueOrDefaultLabel().RemoveCharacters(IsHTMLLineBreak); +} + +bool BaseButtonInputType::ShouldSaveAndRestoreFormControlState() const { + return false; +} + +void BaseButtonInputType::AppendToFormData(FormData&) const {} + +LayoutObject* BaseButtonInputType::CreateLayoutObject( + const ComputedStyle&) const { + return new LayoutButton(&GetElement()); +} + +InputType::ValueMode BaseButtonInputType::GetValueMode() const { + return ValueMode::kDefault; +} + +void BaseButtonInputType::SetValue(const String& sanitized_value, + bool, + TextFieldEventBehavior, + TextControlSetValueSelection) { + GetElement().setAttribute(valueAttr, AtomicString(sanitized_value)); +} + +bool BaseButtonInputType::MatchesDefaultPseudoClass() { + // HTMLFormElement::findDefaultButton() traverses the tree. So we check + // canBeSuccessfulSubmitButton() first for early return. + return CanBeSuccessfulSubmitButton() && GetElement().Form() && + GetElement().Form()->FindDefaultButton() == &GetElement(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h new file mode 100644 index 00000000000..829e130cbce --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_button_input_type.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_BUTTON_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_BUTTON_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h" + +namespace blink { + +// Base of button, image, reset, and submit types. +class BaseButtonInputType : public InputType, + public KeyboardClickableInputTypeView { + USING_GARBAGE_COLLECTED_MIXIN(BaseButtonInputType); + + public: + void Trace(blink::Visitor*) override; + using InputType::GetElement; + + protected: + explicit BaseButtonInputType(HTMLInputElement&); + void ValueAttributeChanged() override; + void CreateShadowSubtree() override; + + private: + InputTypeView* CreateView() override; + bool ShouldSaveAndRestoreFormControlState() const override; + void AppendToFormData(FormData&) const override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + ValueMode GetValueMode() const override; + void SetValue(const String&, + bool, + TextFieldEventBehavior, + TextControlSetValueSelection) override; + bool MatchesDefaultPseudoClass() override; + + String DisplayValue() const; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_BUTTON_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc new file mode 100644 index 00000000000..4b4a558300f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.cc @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/base_checkable_input_type.h" + +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +using namespace HTMLNames; + +void BaseCheckableInputType::Trace(blink::Visitor* visitor) { + InputTypeView::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* BaseCheckableInputType::CreateView() { + return this; +} + +FormControlState BaseCheckableInputType::SaveFormControlState() const { + return FormControlState(GetElement().checked() ? "on" : "off"); +} + +void BaseCheckableInputType::RestoreFormControlState( + const FormControlState& state) { + GetElement().setChecked(state[0] == "on"); +} + +void BaseCheckableInputType::AppendToFormData(FormData& form_data) const { + if (GetElement().checked()) + form_data.append(GetElement().GetName(), GetElement().value()); +} + +void BaseCheckableInputType::HandleKeydownEvent(KeyboardEvent* event) { + const String& key = event->key(); + if (key == " ") { + GetElement().SetActive(true); + // No setDefaultHandled(), because IE dispatches a keypress in this case + // and the caller will only dispatch a keypress if we don't call + // setDefaultHandled(). + } +} + +void BaseCheckableInputType::HandleKeypressEvent(KeyboardEvent* event) { + if (event->charCode() == ' ') { + // Prevent scrolling down the page. + event->SetDefaultHandled(); + } +} + +bool BaseCheckableInputType::CanSetStringValue() const { + return false; +} + +// FIXME: Could share this with KeyboardClickableInputTypeView and +// RangeInputType if we had a common base class. +void BaseCheckableInputType::AccessKeyAction(bool send_mouse_events) { + InputTypeView::AccessKeyAction(send_mouse_events); + + GetElement().DispatchSimulatedClick( + nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents); +} + +bool BaseCheckableInputType::MatchesDefaultPseudoClass() { + return GetElement().FastHasAttribute(checkedAttr); +} + +InputType::ValueMode BaseCheckableInputType::GetValueMode() const { + return ValueMode::kDefaultOn; +} + +void BaseCheckableInputType::SetValue(const String& sanitized_value, + bool, + TextFieldEventBehavior, + TextControlSetValueSelection) { + GetElement().setAttribute(valueAttr, AtomicString(sanitized_value)); +} + +void BaseCheckableInputType::ReadingChecked() const { + if (is_in_click_handler_) { + UseCounter::Count(GetElement().GetDocument(), + WebFeature::kReadingCheckedInClickHandler); + } +} + +bool BaseCheckableInputType::IsCheckable() { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h new file mode 100644 index 00000000000..95d84ae6052 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_checkable_input_type.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_CHECKABLE_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_CHECKABLE_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/input_type_view.h" + +namespace blink { + +// Base of checkbox and radio types. +class BaseCheckableInputType : public InputType, public InputTypeView { + USING_GARBAGE_COLLECTED_MIXIN(BaseCheckableInputType); + + public: + void Trace(blink::Visitor*) override; + using InputType::GetElement; + + protected: + BaseCheckableInputType(HTMLInputElement& element) + : InputType(element), + InputTypeView(element), + is_in_click_handler_(false) {} + void HandleKeydownEvent(KeyboardEvent*) override; + bool NeedsShadowSubtree() const override { return false; } + + bool is_in_click_handler_; + + private: + InputTypeView* CreateView() override; + FormControlState SaveFormControlState() const final; + void RestoreFormControlState(const FormControlState&) final; + void AppendToFormData(FormData&) const final; + void HandleKeypressEvent(KeyboardEvent*) final; + bool CanSetStringValue() const final; + void AccessKeyAction(bool send_mouse_events) final; + bool MatchesDefaultPseudoClass() override; + ValueMode GetValueMode() const override; + void SetValue(const String&, + bool, + TextFieldEventBehavior, + TextControlSetValueSelection) final; + void ReadingChecked() const final; + bool IsCheckable() final; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_CHECKABLE_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc new file mode 100644 index 00000000000..6305600fa10 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.cc @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" + +#include <limits> +#include "third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +using blink::WebLocalizedString; +using namespace HTMLNames; + +static const int kMsecPerMinute = 60 * 1000; +static const int kMsecPerSecond = 1000; + +String BaseTemporalInputType::BadInputText() const { + return GetLocale().QueryString( + WebLocalizedString::kValidationBadInputForDateTime); +} + +InputTypeView* BaseTemporalInputType::CreateView() { + if (RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled()) + return MultipleFieldsTemporalInputTypeView::Create(GetElement(), *this); + return ChooserOnlyTemporalInputTypeView::Create(GetElement(), *this); +} + +InputType::ValueMode BaseTemporalInputType::GetValueMode() const { + return ValueMode::kValue; +} + +double BaseTemporalInputType::ValueAsDate() const { + return ValueAsDouble(); +} + +void BaseTemporalInputType::SetValueAsDate(double value, + ExceptionState&) const { + GetElement().setValue(SerializeWithMilliseconds(value)); +} + +double BaseTemporalInputType::ValueAsDouble() const { + const Decimal value = ParseToNumber(GetElement().value(), Decimal::Nan()); + return value.IsFinite() ? value.ToDouble() + : DateComponents::InvalidMilliseconds(); +} + +void BaseTemporalInputType::SetValueAsDouble( + double new_value, + TextFieldEventBehavior event_behavior, + ExceptionState& exception_state) const { + SetValueAsDecimal(Decimal::FromDouble(new_value), event_behavior, + exception_state); +} + +bool BaseTemporalInputType::TypeMismatchFor(const String& value) const { + return !value.IsEmpty() && !ParseToDateComponents(value, nullptr); +} + +bool BaseTemporalInputType::TypeMismatch() const { + return TypeMismatchFor(GetElement().value()); +} + +String BaseTemporalInputType::RangeOverflowText(const Decimal& maximum) const { + return GetLocale().QueryString( + WebLocalizedString::kValidationRangeOverflowDateTime, + LocalizeValue(Serialize(maximum))); +} + +String BaseTemporalInputType::RangeUnderflowText(const Decimal& minimum) const { + return GetLocale().QueryString( + WebLocalizedString::kValidationRangeUnderflowDateTime, + LocalizeValue(Serialize(minimum))); +} + +Decimal BaseTemporalInputType::DefaultValueForStepUp() const { + return Decimal::FromDouble(ConvertToLocalTime(CurrentTimeMS())); +} + +bool BaseTemporalInputType::IsSteppable() const { + return true; +} + +Decimal BaseTemporalInputType::ParseToNumber( + const String& source, + const Decimal& default_value) const { + DateComponents date; + if (!ParseToDateComponents(source, &date)) + return default_value; + double msec = date.MillisecondsSinceEpoch(); + DCHECK(std::isfinite(msec)); + return Decimal::FromDouble(msec); +} + +bool BaseTemporalInputType::ParseToDateComponents(const String& source, + DateComponents* out) const { + if (source.IsEmpty()) + return false; + DateComponents ignored_result; + if (!out) + out = &ignored_result; + return ParseToDateComponentsInternal(source, out); +} + +String BaseTemporalInputType::Serialize(const Decimal& value) const { + if (!value.IsFinite()) + return String(); + DateComponents date; + if (!SetMillisecondToDateComponents(value.ToDouble(), &date)) + return String(); + return SerializeWithComponents(date); +} + +String BaseTemporalInputType::SerializeWithComponents( + const DateComponents& date) const { + Decimal step; + if (!GetElement().GetAllowedValueStep(&step)) + return date.ToString(); + if (step.Remainder(kMsecPerMinute).IsZero()) + return date.ToString(DateComponents::kNone); + if (step.Remainder(kMsecPerSecond).IsZero()) + return date.ToString(DateComponents::kSecond); + return date.ToString(DateComponents::kMillisecond); +} + +String BaseTemporalInputType::SerializeWithMilliseconds(double value) const { + return Serialize(Decimal::FromDouble(value)); +} + +String BaseTemporalInputType::LocalizeValue( + const String& proposed_value) const { + DateComponents date; + if (!ParseToDateComponents(proposed_value, &date)) + return proposed_value; + + String localized = GetElement().GetLocale().FormatDateTime(date); + return localized.IsEmpty() ? proposed_value : localized; +} + +String BaseTemporalInputType::VisibleValue() const { + return LocalizeValue(GetElement().value()); +} + +String BaseTemporalInputType::SanitizeValue( + const String& proposed_value) const { + return TypeMismatchFor(proposed_value) ? g_empty_string : proposed_value; +} + +bool BaseTemporalInputType::SupportsReadOnly() const { + return true; +} + +bool BaseTemporalInputType::ShouldRespectListAttribute() { + return true; +} + +bool BaseTemporalInputType::ValueMissing(const String& value) const { + return GetElement().IsRequired() && value.IsEmpty(); +} + +bool BaseTemporalInputType::ShouldShowFocusRingOnMouseFocus() const { + return true; +} + +bool BaseTemporalInputType::ShouldHaveSecondField( + const DateComponents& date) const { + StepRange step_range = CreateStepRange(kAnyIsDefaultStep); + return date.Second() || date.Millisecond() || + !step_range.Minimum() + .Remainder(static_cast<int>(kMsPerMinute)) + .IsZero() || + !step_range.Step().Remainder(static_cast<int>(kMsPerMinute)).IsZero(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h new file mode 100644 index 00000000000..72617e3af31 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_temporal_input_type.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEMPORAL_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEMPORAL_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/date_time_edit_element.h" +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/platform/date_components.h" + +namespace blink { + +class ExceptionState; + +// A super class of date, datetime, datetime-local, month, time, and week types. +// TODO(tkent): A single temporal input type creates two InputTypeView instances +// unnecessarily. One is ChooserOnlyTemporalInputTypeView or +// MultipleFieldsTemporalInputType, and another is BaseTemporalInputType, which +// inherits from InputTypeView through InputType. The latter is not used. +class BaseTemporalInputType : public InputType { + public: + String VisibleValue() const override; + String SanitizeValue(const String&) const override; + // Parses the specified string for this InputType, and returns true if it + // is successfully parsed. An instance pointed by the DateComponents* + // parameter will have parsed values and be modified even if the parsing + // fails. The DateComponents* parameter may be 0. + bool ParseToDateComponents(const String&, DateComponents*) const; + virtual bool SetMillisecondToDateComponents(double, + DateComponents*) const = 0; + + // Provide some helpers for MultipleFieldsTemporalInputTypeView. + virtual String FormatDateTimeFieldsState( + const DateTimeFieldsState&) const = 0; + virtual void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&, + const DateComponents&) const = 0; + virtual bool IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const = 0; + + protected: + BaseTemporalInputType(HTMLInputElement& element) : InputType(element) {} + Decimal ParseToNumber(const String&, const Decimal&) const override; + String Serialize(const Decimal&) const override; + String SerializeWithComponents(const DateComponents&) const; + bool ShouldHaveSecondField(const DateComponents&) const; + + private: + virtual bool ParseToDateComponentsInternal(const String&, + DateComponents*) const = 0; + + String BadInputText() const override; + InputTypeView* CreateView() override; + ValueMode GetValueMode() const override; + double ValueAsDate() const override; + void SetValueAsDate(double, ExceptionState&) const override; + double ValueAsDouble() const override; + void SetValueAsDouble(double, + TextFieldEventBehavior, + ExceptionState&) const override; + bool TypeMismatchFor(const String&) const override; + bool TypeMismatch() const override; + bool ValueMissing(const String&) const override; + String RangeOverflowText(const Decimal& maximum) const override; + String RangeUnderflowText(const Decimal& minimum) const override; + Decimal DefaultValueForStepUp() const override; + bool IsSteppable() const override; + virtual String SerializeWithMilliseconds(double) const; + String LocalizeValue(const String&) const override; + bool SupportsReadOnly() const override; + bool ShouldRespectListAttribute() override; + bool ShouldShowFocusRingOnMouseFocus() const override; +}; + +} // namespace blink +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEMPORAL_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc new file mode 100644 index 00000000000..5f167ebf233 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.cc @@ -0,0 +1,123 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com> + * Copyright (C) 2010 Google Inc. 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 "third_party/blink/renderer/core/html/forms/base_text_input_type.h" + +#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" + +namespace blink { + +using namespace HTMLNames; + +BaseTextInputType::BaseTextInputType(HTMLInputElement& element) + : TextFieldInputType(element) {} + +BaseTextInputType::~BaseTextInputType() = default; + +int BaseTextInputType::MaxLength() const { + return GetElement().maxLength(); +} + +int BaseTextInputType::MinLength() const { + return GetElement().minLength(); +} + +bool BaseTextInputType::TooLong( + const String& value, + TextControlElement::NeedsToCheckDirtyFlag check) const { + int max = GetElement().maxLength(); + if (max < 0) + return false; + if (check == TextControlElement::kCheckDirtyFlag) { + // Return false for the default value or a value set by a script even if + // it is longer than maxLength. + if (!GetElement().HasDirtyValue() || !GetElement().LastChangeWasUserEdit()) + return false; + } + return value.length() > static_cast<unsigned>(max); +} + +bool BaseTextInputType::TooShort( + const String& value, + TextControlElement::NeedsToCheckDirtyFlag check) const { + int min = GetElement().minLength(); + if (min <= 0) + return false; + if (check == TextControlElement::kCheckDirtyFlag) { + // Return false for the default value or a value set by a script even if + // it is shorter than minLength. + if (!GetElement().HasDirtyValue() || !GetElement().LastChangeWasUserEdit()) + return false; + } + // An empty string is excluded from minlength check. + unsigned len = value.length(); + return len > 0 && len < static_cast<unsigned>(min); +} + +bool BaseTextInputType::PatternMismatch(const String& value) const { + const AtomicString& raw_pattern = GetElement().FastGetAttribute(patternAttr); + // Empty values can't be mismatched + if (raw_pattern.IsNull() || value.IsEmpty()) + return false; + if (!regexp_ || pattern_for_regexp_ != raw_pattern) { + std::unique_ptr<ScriptRegexp> raw_regexp( + new ScriptRegexp(raw_pattern, kTextCaseSensitive, kMultilineDisabled, + ScriptRegexp::UTF16)); + if (!raw_regexp->IsValid()) { + GetElement().GetDocument().AddConsoleMessage( + ConsoleMessage::Create(kRenderingMessageSource, kErrorMessageLevel, + "Pattern attribute value " + raw_pattern + + " is not a valid regular expression: " + + raw_regexp->ExceptionMessage())); + regexp_.reset(raw_regexp.release()); + pattern_for_regexp_ = raw_pattern; + return false; + } + String pattern = "^(?:" + raw_pattern + ")$"; + regexp_.reset(new ScriptRegexp(pattern, kTextCaseSensitive, + kMultilineDisabled, ScriptRegexp::UTF16)); + pattern_for_regexp_ = raw_pattern; + } else if (!regexp_->IsValid()) { + return false; + } + + int match_length = 0; + int value_length = value.length(); + int match_offset = regexp_->Match(value, 0, &match_length); + bool mismatched = match_offset != 0 || match_length != value_length; + return mismatched; +} + +bool BaseTextInputType::SupportsPlaceholder() const { + return true; +} + +bool BaseTextInputType::SupportsSelectionAPI() const { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h new file mode 100644 index 00000000000..2d252058a9f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/base_text_input_type.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEXT_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEXT_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/text_field_input_type.h" + +namespace blink { + +class ScriptRegexp; + +// Base of email, password, search, tel, text, and URL types. +// They support maxlength, selection functions, and so on. +class BaseTextInputType : public TextFieldInputType { + protected: + BaseTextInputType(HTMLInputElement&); + ~BaseTextInputType() override; + + private: + bool TooLong(const String&, + TextControlElement::NeedsToCheckDirtyFlag) const final; + bool TooShort(const String&, + TextControlElement::NeedsToCheckDirtyFlag) const final; + int MaxLength() const final; + int MinLength() const final; + bool PatternMismatch(const String&) const final; + bool SupportsPlaceholder() const final; + bool SupportsSelectionAPI() const override; + + // m_regexp and m_patternForRegexp are mutable because they are kinds of + // cache. + mutable std::unique_ptr<ScriptRegexp> regexp_; + mutable AtomicString pattern_for_regexp_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BASE_TEXT_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc new file mode 100644 index 00000000000..7c58447907d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.cc @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/button_input_type.h" + +#include "third_party/blink/renderer/core/input_type_names.h" + +namespace blink { + +InputType* ButtonInputType::Create(HTMLInputElement& element) { + return new ButtonInputType(element); +} + +const AtomicString& ButtonInputType::FormControlType() const { + return InputTypeNames::button; +} + +bool ButtonInputType::SupportsValidation() const { + return false; +} + +bool ButtonInputType::IsTextButton() const { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/button_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.h new file mode 100644 index 00000000000..479bd80655c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/button_input_type.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BUTTON_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BUTTON_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h" + +namespace blink { + +class ButtonInputType final : public BaseButtonInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + ButtonInputType(HTMLInputElement& element) : BaseButtonInputType(element) {} + const AtomicString& FormControlType() const override; + bool SupportsValidation() const override; + bool IsTextButton() const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_BUTTON_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc new file mode 100644 index 00000000000..db7b034b596 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/checkbox_input_type.h" + +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +InputType* CheckboxInputType::Create(HTMLInputElement& element) { + return new CheckboxInputType(element); +} + +const AtomicString& CheckboxInputType::FormControlType() const { + return InputTypeNames::checkbox; +} + +bool CheckboxInputType::ValueMissing(const String&) const { + return GetElement().IsRequired() && !GetElement().checked(); +} + +String CheckboxInputType::ValueMissingText() const { + return GetLocale().QueryString( + WebLocalizedString::kValidationValueMissingForCheckbox); +} + +void CheckboxInputType::HandleKeyupEvent(KeyboardEvent* event) { + const String& key = event->key(); + if (key != " ") + return; + DispatchSimulatedClickIfActive(event); +} + +ClickHandlingState* CheckboxInputType::WillDispatchClick() { + // An event handler can use preventDefault or "return false" to reverse the + // checking we do here. The ClickHandlingState object contains what we need + // to undo what we did here in didDispatchClick. + + ClickHandlingState* state = new ClickHandlingState; + + state->checked = GetElement().checked(); + state->indeterminate = GetElement().indeterminate(); + + if (state->indeterminate) + GetElement().setIndeterminate(false); + + GetElement().setChecked(!state->checked, kDispatchChangeEvent); + is_in_click_handler_ = true; + return state; +} + +void CheckboxInputType::DidDispatchClick(Event* event, + const ClickHandlingState& state) { + if (event->defaultPrevented() || event->DefaultHandled()) { + GetElement().setIndeterminate(state.indeterminate); + GetElement().setChecked(state.checked); + } else { + GetElement().DispatchInputAndChangeEventIfNeeded(); + } + is_in_click_handler_ = false; + // The work we did in willDispatchClick was default handling. + event->SetDefaultHandled(); +} + +bool CheckboxInputType::ShouldAppearIndeterminate() const { + return GetElement().indeterminate(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h new file mode 100644 index 00000000000..ae088c656ef --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/checkbox_input_type.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHECKBOX_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHECKBOX_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_checkable_input_type.h" + +namespace blink { + +class CheckboxInputType final : public BaseCheckableInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + CheckboxInputType(HTMLInputElement& element) + : BaseCheckableInputType(element) {} + const AtomicString& FormControlType() const override; + bool ValueMissing(const String&) const override; + String ValueMissingText() const override; + void HandleKeyupEvent(KeyboardEvent*) override; + ClickHandlingState* WillDispatchClick() override; + void DidDispatchClick(Event*, const ClickHandlingState&) override; + bool ShouldAppearIndeterminate() const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHECKBOX_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc new file mode 100644 index 00000000000..785db8e8066 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" + +namespace blink { + +ChooserOnlyTemporalInputTypeView::ChooserOnlyTemporalInputTypeView( + HTMLInputElement& element, + BaseTemporalInputType& input_type) + : KeyboardClickableInputTypeView(element), input_type_(input_type) {} + +ChooserOnlyTemporalInputTypeView* ChooserOnlyTemporalInputTypeView::Create( + HTMLInputElement& element, + BaseTemporalInputType& input_type) { + return new ChooserOnlyTemporalInputTypeView(element, input_type); +} + +ChooserOnlyTemporalInputTypeView::~ChooserOnlyTemporalInputTypeView() { + DCHECK(!date_time_chooser_); +} + +void ChooserOnlyTemporalInputTypeView::Trace(blink::Visitor* visitor) { + visitor->Trace(input_type_); + visitor->Trace(date_time_chooser_); + InputTypeView::Trace(visitor); + DateTimeChooserClient::Trace(visitor); +} + +void ChooserOnlyTemporalInputTypeView::HandleDOMActivateEvent(Event* event) { + Document& document = GetElement().GetDocument(); + if (GetElement().IsDisabledOrReadOnly() || !GetElement().GetLayoutObject() || + !Frame::HasTransientUserActivation(document.GetFrame()) || + GetElement().OpenShadowRoot()) + return; + + if (date_time_chooser_) + return; + if (!document.IsActive()) + return; + DateTimeChooserParameters parameters; + if (!GetElement().SetupDateTimeChooserParameters(parameters)) + return; + UseCounter::Count( + document, + (event->UnderlyingEvent() && event->UnderlyingEvent()->isTrusted()) + ? WebFeature::kTemporalInputTypeChooserByTrustedClick + : WebFeature::kTemporalInputTypeChooserByUntrustedClick); + date_time_chooser_ = + document.GetPage()->GetChromeClient().OpenDateTimeChooser(this, + parameters); +} + +void ChooserOnlyTemporalInputTypeView::CreateShadowSubtree() { + DEFINE_STATIC_LOCAL(AtomicString, value_container_pseudo, + ("-webkit-date-and-time-value")); + + HTMLDivElement* value_container = + HTMLDivElement::Create(GetElement().GetDocument()); + value_container->SetShadowPseudoId(value_container_pseudo); + GetElement().UserAgentShadowRoot()->AppendChild(value_container); + UpdateView(); +} + +void ChooserOnlyTemporalInputTypeView::UpdateView() { + Node* node = GetElement().UserAgentShadowRoot()->firstChild(); + if (!node || !node->IsHTMLElement()) + return; + String display_value; + if (!GetElement().SuggestedValue().IsNull()) + display_value = GetElement().SuggestedValue(); + else + display_value = input_type_->VisibleValue(); + if (display_value.IsEmpty()) { + // Need to put something to keep text baseline. + display_value = " "; + } + ToHTMLElement(node)->setTextContent(display_value); +} + +void ChooserOnlyTemporalInputTypeView::DidSetValue(const String& value, + bool value_changed) { + if (value_changed) + UpdateView(); +} + +void ChooserOnlyTemporalInputTypeView::ClosePopupView() { + CloseDateTimeChooser(); +} + +Element& ChooserOnlyTemporalInputTypeView::OwnerElement() const { + return GetElement(); +} + +void ChooserOnlyTemporalInputTypeView::DidChooseValue(const String& value) { + GetElement().setValue(value, kDispatchInputAndChangeEvent); +} + +void ChooserOnlyTemporalInputTypeView::DidChooseValue(double value) { + DCHECK(std::isfinite(value) || std::isnan(value)); + if (std::isnan(value)) + GetElement().setValue(g_empty_string, kDispatchInputAndChangeEvent); + else + GetElement().setValueAsNumber(value, ASSERT_NO_EXCEPTION, + kDispatchInputAndChangeEvent); +} + +void ChooserOnlyTemporalInputTypeView::DidEndChooser() { + date_time_chooser_.Clear(); +} + +void ChooserOnlyTemporalInputTypeView::CloseDateTimeChooser() { + if (date_time_chooser_) + date_time_chooser_->EndChooser(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h new file mode 100644 index 00000000000..9dbfb0ae2fe --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHOOSER_ONLY_TEMPORAL_INPUT_TYPE_VIEW_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHOOSER_ONLY_TEMPORAL_INPUT_TYPE_VIEW_H_ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" +#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class ChooserOnlyTemporalInputTypeView final + : public GarbageCollectedFinalized<ChooserOnlyTemporalInputTypeView>, + public KeyboardClickableInputTypeView, + public DateTimeChooserClient { + USING_GARBAGE_COLLECTED_MIXIN(ChooserOnlyTemporalInputTypeView); + USING_PRE_FINALIZER(ChooserOnlyTemporalInputTypeView, CloseDateTimeChooser); + + public: + static ChooserOnlyTemporalInputTypeView* Create(HTMLInputElement&, + BaseTemporalInputType&); + ~ChooserOnlyTemporalInputTypeView() override; + void Trace(blink::Visitor*) override; + + private: + ChooserOnlyTemporalInputTypeView(HTMLInputElement&, BaseTemporalInputType&); + void CloseDateTimeChooser(); + + // InputTypeView functions: + void CreateShadowSubtree() override; + void ClosePopupView() override; + void DidSetValue(const String&, bool value_changed) override; + void HandleDOMActivateEvent(Event*) override; + void UpdateView() override; + + // DateTimeChooserClient functions: + Element& OwnerElement() const override; + void DidChooseValue(const String&) override; + void DidChooseValue(double) override; + void DidEndChooser() override; + + Member<BaseTemporalInputType> input_type_; + Member<DateTimeChooser> date_time_chooser_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CHOOSER_ONLY_TEMPORAL_INPUT_TYPE_VIEW_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc new file mode 100644 index 00000000000..7e92b7e68e5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.cc @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/clear_button_element.h" + +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" + +namespace blink { + +using namespace HTMLNames; + +inline ClearButtonElement::ClearButtonElement( + Document& document, + ClearButtonOwner& clear_button_owner) + : HTMLDivElement(document), clear_button_owner_(&clear_button_owner) {} + +ClearButtonElement* ClearButtonElement::Create( + Document& document, + ClearButtonOwner& clear_button_owner) { + ClearButtonElement* element = + new ClearButtonElement(document, clear_button_owner); + element->SetShadowPseudoId(AtomicString("-webkit-clear-button")); + element->setAttribute(idAttr, ShadowElementNames::ClearButton()); + return element; +} + +void ClearButtonElement::DetachLayoutTree(const AttachContext& context) { + HTMLDivElement::DetachLayoutTree(context); +} + +void ClearButtonElement::DefaultEventHandler(Event* event) { + if (!clear_button_owner_) { + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); + return; + } + + if (!clear_button_owner_->ShouldClearButtonRespondToMouseEvents()) { + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); + return; + } + + if (event->type() == EventTypeNames::click) { + if (GetLayoutObject() && GetLayoutObject()->VisibleToHitTesting()) { + clear_button_owner_->FocusAndSelectClearButtonOwner(); + clear_button_owner_->ClearValue(); + event->SetDefaultHandled(); + } + } + + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); +} + +bool ClearButtonElement::IsClearButtonElement() const { + return true; +} + +void ClearButtonElement::Trace(blink::Visitor* visitor) { + visitor->Trace(clear_button_owner_); + HTMLDivElement::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h new file mode 100644 index 00000000000..4698422d545 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/clear_button_element.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CLEAR_BUTTON_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CLEAR_BUTTON_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class ClearButtonElement final : public HTMLDivElement { + public: + class ClearButtonOwner : public GarbageCollectedMixin { + public: + virtual ~ClearButtonOwner() = default; + virtual void FocusAndSelectClearButtonOwner() = 0; + virtual bool ShouldClearButtonRespondToMouseEvents() = 0; + virtual void ClearValue() = 0; + }; + + static ClearButtonElement* Create(Document&, ClearButtonOwner&); + void RemoveClearButtonOwner() { clear_button_owner_ = nullptr; } + + void Trace(blink::Visitor*) override; + + private: + ClearButtonElement(Document&, ClearButtonOwner&); + void DetachLayoutTree(const AttachContext& = AttachContext()) override; + bool IsMouseFocusable() const override { return false; } + void DefaultEventHandler(Event*) override; + bool IsClearButtonElement() const override; + + Member<ClearButtonOwner> clear_button_owner_; +}; + +DEFINE_TYPE_CASTS(ClearButtonElement, + Element, + element, + element->IsClearButtonElement(), + element.IsClearButtonElement()); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_CLEAR_BUTTON_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc new file mode 100644 index 00000000000..051b0ecf604 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Google, Inc. ("Google") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "third_party/blink/renderer/core/html/forms/color_chooser.h" + +namespace blink { + +ColorChooser::ColorChooser() = default; + +ColorChooser::~ColorChooser() = default; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h new file mode 100644 index 00000000000..72012762a3d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class AXObject; +class Color; + +class CORE_EXPORT ColorChooser : public GarbageCollectedMixin { + public: + ColorChooser(); + virtual ~ColorChooser(); + void Trace(blink::Visitor* visitor) override {} + + virtual void SetSelectedColor(const Color&) {} + virtual void EndChooser() {} + // Returns a root AXObject in the ColorChooser if it's available. + virtual AXObject* RootAXObject() = 0; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc new file mode 100644 index 00000000000..4033d4622d5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.cc @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Google, Inc. ("Google") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" + +namespace blink { + +ColorChooserClient::~ColorChooserClient() = default; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h new file mode 100644 index 00000000000..845c5cca368 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_client.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_CLIENT_H_ + +#include "third_party/blink/public/mojom/color_chooser/color_chooser.mojom-blink.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/graphics/color.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class Element; + +class CORE_EXPORT ColorChooserClient : public GarbageCollectedMixin { + public: + virtual ~ColorChooserClient(); + void Trace(blink::Visitor* visitor) override {} + + virtual void DidChooseColor(const Color&) = 0; + virtual void DidEndChooser() = 0; + virtual Element& OwnerElement() const = 0; + virtual IntRect ElementRectRelativeToViewport() const = 0; + virtual Color CurrentColor() = 0; + virtual bool ShouldShowSuggestions() const = 0; + virtual Vector<mojom::blink::ColorSuggestionPtr> Suggestions() const = 0; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc new file mode 100644 index 00000000000..129853be04b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h" + +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page_popup.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" + +namespace blink { + +// Keep in sync with Actions in colorSuggestionPicker.js. +enum ColorPickerPopupAction { + kColorPickerPopupActionChooseOtherColor = -2, + kColorPickerPopupActionCancel = -1, + kColorPickerPopupActionSetValue = 0 +}; + +ColorChooserPopupUIController::ColorChooserPopupUIController( + LocalFrame* frame, + ChromeClient* chrome_client, + blink::ColorChooserClient* client) + : ColorChooserUIController(frame, client), + chrome_client_(chrome_client), + popup_(nullptr), + locale_(Locale::DefaultLocale()) {} + +ColorChooserPopupUIController::~ColorChooserPopupUIController() = default; + +void ColorChooserPopupUIController::Dispose() { + // Finalized earlier so as to access m_chromeClient while alive. + ClosePopup(); + // ~ColorChooserUIController calls endChooser(). +} + +void ColorChooserPopupUIController::Trace(blink::Visitor* visitor) { + visitor->Trace(chrome_client_); + ColorChooserUIController::Trace(visitor); +} + +void ColorChooserPopupUIController::OpenUI() { + if (client_->ShouldShowSuggestions()) + OpenPopup(); + else + OpenColorChooser(); +} + +void ColorChooserPopupUIController::EndChooser() { + ColorChooserUIController::EndChooser(); + ClosePopup(); +} + +AXObject* ColorChooserPopupUIController::RootAXObject() { + return popup_ ? popup_->RootAXObject() : nullptr; +} + +void ColorChooserPopupUIController::WriteDocument(SharedBuffer* data) { + Vector<String> suggestion_values; + for (auto& suggestion : client_->Suggestions()) + suggestion_values.push_back(suggestion->label); + IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen( + client_->ElementRectRelativeToViewport(), frame_->View()); + + PagePopupClient::AddString( + "<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data); + data->Append(Platform::Current()->GetDataResource("pickerCommon.css")); + data->Append( + Platform::Current()->GetDataResource("colorSuggestionPicker.css")); + PagePopupClient::AddString( + "</style></head><body><div id=main>Loading...</div><script>\n" + "window.dialogArguments = {\n", + data); + PagePopupClient::AddProperty("values", suggestion_values, data); + PagePopupClient::AddProperty( + "otherColorLabel", + GetLocale().QueryString(WebLocalizedString::kOtherColorLabel), data); + AddProperty("anchorRectInScreen", anchor_rect_in_screen, data); + AddProperty("zoomFactor", ZoomFactor(), data); + PagePopupClient::AddString("};\n", data); + data->Append(Platform::Current()->GetDataResource("pickerCommon.js")); + data->Append( + Platform::Current()->GetDataResource("colorSuggestionPicker.js")); + PagePopupClient::AddString("</script></body>\n", data); +} + +Locale& ColorChooserPopupUIController::GetLocale() { + return locale_; +} + +void ColorChooserPopupUIController::SetValueAndClosePopup( + int num_value, + const String& string_value) { + DCHECK(popup_); + DCHECK(client_); + if (num_value == kColorPickerPopupActionSetValue) + SetValue(string_value); + if (num_value == kColorPickerPopupActionChooseOtherColor) + OpenColorChooser(); + ClosePopup(); +} + +void ColorChooserPopupUIController::SetValue(const String& value) { + DCHECK(client_); + Color color; + bool success = color.SetFromString(value); + DCHECK(success); + client_->DidChooseColor(color); +} + +void ColorChooserPopupUIController::DidClosePopup() { + popup_ = nullptr; + + if (!chooser_) + EndChooser(); +} + +Element& ColorChooserPopupUIController::OwnerElement() { + return client_->OwnerElement(); +} + +void ColorChooserPopupUIController::OpenPopup() { + DCHECK(!popup_); + popup_ = chrome_client_->OpenPagePopup(this); +} + +void ColorChooserPopupUIController::ClosePopup() { + if (!popup_) + return; + chrome_client_->ClosePagePopup(popup_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h new file mode 100644 index 00000000000..53d750e78d5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_POPUP_UI_CONTROLLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_POPUP_UI_CONTROLLER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h" +#include "third_party/blink/renderer/core/page/page_popup_client.h" + +namespace blink { + +class ChromeClient; +class ColorChooserClient; +class PagePopup; + +class CORE_EXPORT ColorChooserPopupUIController final + : public ColorChooserUIController, + public PagePopupClient { + USING_PRE_FINALIZER(ColorChooserPopupUIController, Dispose); + + public: + static ColorChooserPopupUIController* Create( + LocalFrame* frame, + ChromeClient* chrome_client, + blink::ColorChooserClient* client) { + return new ColorChooserPopupUIController(frame, chrome_client, client); + } + + ~ColorChooserPopupUIController() override; + void Trace(blink::Visitor*) override; + + // ColorChooserUIController functions: + void OpenUI() override; + + // ColorChooser functions + void EndChooser() override; + AXObject* RootAXObject() override; + + // PagePopupClient functions: + void WriteDocument(SharedBuffer*) override; + void SelectFontsFromOwnerDocument(Document&) override {} + Locale& GetLocale() override; + void SetValueAndClosePopup(int, const String&) override; + void SetValue(const String&) override; + void ClosePopup() override; + Element& OwnerElement() override; + void DidClosePopup() override; + + private: + ColorChooserPopupUIController(LocalFrame*, + ChromeClient*, + blink::ColorChooserClient*); + + void OpenPopup(); + void Dispose(); + + Member<ChromeClient> chrome_client_; + PagePopup* popup_; + Locale& locale_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_POPUP_UI_CONTROLLER_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc new file mode 100644 index 00000000000..8ea5c8429ad --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.cc @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h" + +#include "services/service_manager/public/cpp/interface_provider.h" +#include "third_party/blink/public/platform/web_color.h" +#include "third_party/blink/public/web/web_frame_client.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" +#include "third_party/blink/renderer/platform/graphics/color.h" + +namespace blink { + +ColorChooserUIController::ColorChooserUIController( + LocalFrame* frame, + blink::ColorChooserClient* client) + : client_(client), frame_(frame), binding_(this) {} + +ColorChooserUIController::~ColorChooserUIController() {} + +void ColorChooserUIController::Trace(blink::Visitor* visitor) { + visitor->Trace(frame_); + visitor->Trace(client_); + ColorChooser::Trace(visitor); +} + +void ColorChooserUIController::Dispose() { + binding_.Close(); +} + +void ColorChooserUIController::OpenUI() { + OpenColorChooser(); +} + +void ColorChooserUIController::SetSelectedColor(const Color& color) { + // Color can be set via JS before mojo OpenColorChooser completes. + if (chooser_) + chooser_->SetSelectedColor(color.Rgb()); +} + +void ColorChooserUIController::EndChooser() { + chooser_.reset(); + client_->DidEndChooser(); +} + +AXObject* ColorChooserUIController::RootAXObject() { + return nullptr; +} + +void ColorChooserUIController::DidChooseColor(uint32_t color) { + client_->DidChooseColor(color); +} + +void ColorChooserUIController::OpenColorChooser() { + DCHECK(!chooser_); + frame_->GetInterfaceProvider().GetInterface(&color_chooser_factory_); + mojom::blink::ColorChooserClientPtr mojo_client; + binding_.Bind(mojo::MakeRequest(&mojo_client)); + binding_.set_connection_error_handler(WTF::Bind( + &ColorChooserUIController::EndChooser, WrapWeakPersistent(this))); + color_chooser_factory_->OpenColorChooser( + mojo::MakeRequest(&chooser_), std::move(mojo_client), + client_->CurrentColor().Rgb(), client_->Suggestions()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h new file mode 100644 index 00000000000..311c2a26b2b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_chooser_ui_controller.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_ + +#include <memory> +#include "mojo/public/cpp/bindings/binding.h" +#include "third_party/blink/public/mojom/color_chooser/color_chooser.mojom-blink.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +class ColorChooserClient; +class LocalFrame; + +class CORE_EXPORT ColorChooserUIController + : public GarbageCollectedFinalized<ColorChooserUIController>, + public mojom::blink::ColorChooserClient, + public ColorChooser { + USING_GARBAGE_COLLECTED_MIXIN(ColorChooserUIController); + USING_PRE_FINALIZER(ColorChooserUIController, Dispose); + + public: + static ColorChooserUIController* Create(LocalFrame* frame, + blink::ColorChooserClient* client) { + return new ColorChooserUIController(frame, client); + } + + ~ColorChooserUIController() override; + void Trace(blink::Visitor*) override; + + void Dispose(); + + virtual void OpenUI(); + + // ColorChooser functions: + void SetSelectedColor(const Color&) final; + void EndChooser() override; + AXObject* RootAXObject() override; + + // mojom::blink::ColorChooserClient functions: + void DidChooseColor(uint32_t color) final; + + protected: + ColorChooserUIController(LocalFrame*, blink::ColorChooserClient*); + + void OpenColorChooser(); + mojom::blink::ColorChooserPtr chooser_; + Member<blink::ColorChooserClient> client_; + + Member<LocalFrame> frame_; + + private: + mojom::blink::ColorChooserFactoryPtr color_chooser_factory_; + mojo::Binding<mojom::blink::ColorChooserClient> binding_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_CHOOSER_UI_CONTROLLER_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc new file mode 100644 index 00000000000..ab0b2d5e477 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.cc @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/color_input_type.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" +#include "third_party/blink/renderer/core/css_property_names.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/platform/graphics/color.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using namespace HTMLNames; + +// Upper limit of number of datalist suggestions shown. +static const unsigned kMaxSuggestions = 1000; +// Upper limit for the length of the labels for datalist suggestions. +static const unsigned kMaxSuggestionLabelLength = 1000; + +static bool IsValidColorString(const String& value) { + if (value.IsEmpty()) + return false; + if (value[0] != '#') + return false; + + // We don't accept #rgb and #aarrggbb formats. + if (value.length() != 7) + return false; + Color color; + return color.SetFromString(value) && !color.HasAlpha(); +} + +ColorInputType::ColorInputType(HTMLInputElement& element) + : InputType(element), KeyboardClickableInputTypeView(element) {} + +InputType* ColorInputType::Create(HTMLInputElement& element) { + return new ColorInputType(element); +} + +ColorInputType::~ColorInputType() = default; + +void ColorInputType::Trace(blink::Visitor* visitor) { + visitor->Trace(chooser_); + KeyboardClickableInputTypeView::Trace(visitor); + ColorChooserClient::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* ColorInputType::CreateView() { + return this; +} + +InputType::ValueMode ColorInputType::GetValueMode() const { + return ValueMode::kValue; +} + +void ColorInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeColor); +} + +const AtomicString& ColorInputType::FormControlType() const { + return InputTypeNames::color; +} + +bool ColorInputType::SupportsRequired() const { + return false; +} + +String ColorInputType::SanitizeValue(const String& proposed_value) const { + if (!IsValidColorString(proposed_value)) + return "#000000"; + return proposed_value.DeprecatedLower(); +} + +Color ColorInputType::ValueAsColor() const { + Color color; + bool success = color.SetFromString(GetElement().value()); + DCHECK(success); + return color; +} + +void ColorInputType::CreateShadowSubtree() { + DCHECK(IsShadowHost(GetElement())); + + Document& document = GetElement().GetDocument(); + HTMLDivElement* wrapper_element = HTMLDivElement::Create(document); + wrapper_element->SetShadowPseudoId( + AtomicString("-webkit-color-swatch-wrapper")); + HTMLDivElement* color_swatch = HTMLDivElement::Create(document); + color_swatch->SetShadowPseudoId(AtomicString("-webkit-color-swatch")); + wrapper_element->AppendChild(color_swatch); + GetElement().UserAgentShadowRoot()->AppendChild(wrapper_element); + + GetElement().UpdateView(); +} + +void ColorInputType::DidSetValue(const String&, bool value_changed) { + if (!value_changed) + return; + GetElement().UpdateView(); + if (chooser_) + chooser_->SetSelectedColor(ValueAsColor()); +} + +void ColorInputType::HandleDOMActivateEvent(Event* event) { + if (GetElement().IsDisabledFormControl()) + return; + + Document& document = GetElement().GetDocument(); + if (!Frame::HasTransientUserActivation(document.GetFrame())) + return; + + ChromeClient* chrome_client = GetChromeClient(); + if (chrome_client && !chooser_) { + UseCounter::Count( + document, + (event->UnderlyingEvent() && event->UnderlyingEvent()->isTrusted()) + ? WebFeature::kColorInputTypeChooserByTrustedClick + : WebFeature::kColorInputTypeChooserByUntrustedClick); + chooser_ = chrome_client->OpenColorChooser(document.GetFrame(), this, + ValueAsColor()); + } + + event->SetDefaultHandled(); +} + +void ColorInputType::ClosePopupView() { + EndColorChooser(); +} + +bool ColorInputType::ShouldRespectListAttribute() { + return true; +} + +bool ColorInputType::TypeMismatchFor(const String& value) const { + return !IsValidColorString(value); +} + +void ColorInputType::WarnIfValueIsInvalid(const String& value) const { + if (!DeprecatedEqualIgnoringCase(value, GetElement().SanitizeValue(value))) + AddWarningToConsole( + "The specified value %s does not conform to the required format. The " + "format is \"#rrggbb\" where rr, gg, bb are two-digit hexadecimal " + "numbers.", + value); +} + +void ColorInputType::ValueAttributeChanged() { + if (!GetElement().HasDirtyValue()) + GetElement().UpdateView(); +} + +void ColorInputType::DidChooseColor(const Color& color) { + if (GetElement().IsDisabledFormControl() || color == ValueAsColor()) + return; + EventQueueScope scope; + GetElement().SetValueFromRenderer(color.Serialized()); + GetElement().UpdateView(); + if (!LayoutTheme::GetTheme().IsModalColorChooser()) + GetElement().DispatchFormControlChangeEvent(); +} + +void ColorInputType::DidEndChooser() { + if (LayoutTheme::GetTheme().IsModalColorChooser()) + GetElement().EnqueueChangeEvent(); + chooser_.Clear(); +} + +void ColorInputType::EndColorChooser() { + if (chooser_) + chooser_->EndChooser(); +} + +void ColorInputType::UpdateView() { + HTMLElement* color_swatch = ShadowColorSwatch(); + if (!color_swatch) + return; + + color_swatch->SetInlineStyleProperty(CSSPropertyBackgroundColor, + GetElement().value()); +} + +HTMLElement* ColorInputType::ShadowColorSwatch() const { + ShadowRoot* shadow = GetElement().UserAgentShadowRoot(); + return shadow ? ToHTMLElementOrDie(shadow->firstChild()->firstChild()) + : nullptr; +} + +Element& ColorInputType::OwnerElement() const { + return GetElement(); +} + +IntRect ColorInputType::ElementRectRelativeToViewport() const { + return GetElement().GetDocument().View()->ContentsToViewport( + GetElement().PixelSnappedBoundingBox()); +} + +Color ColorInputType::CurrentColor() { + return ValueAsColor(); +} + +bool ColorInputType::ShouldShowSuggestions() const { + return GetElement().FastHasAttribute(listAttr); +} + +Vector<mojom::blink::ColorSuggestionPtr> ColorInputType::Suggestions() const { + Vector<mojom::blink::ColorSuggestionPtr> suggestions; + HTMLDataListElement* data_list = GetElement().DataList(); + if (data_list) { + HTMLDataListOptionsCollection* options = data_list->options(); + for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); i++) { + if (option->IsDisabledFormControl() || option->value().IsEmpty()) + continue; + if (!GetElement().IsValidValue(option->value())) + continue; + Color color; + if (!color.SetFromString(option->value())) + continue; + suggestions.push_back(mojom::blink::ColorSuggestion::New( + color.Rgb(), option->label().Left(kMaxSuggestionLabelLength))); + if (suggestions.size() >= kMaxSuggestions) + break; + } + } + return suggestions; +} + +AXObject* ColorInputType::PopupRootAXObject() { + return chooser_ ? chooser_->RootAXObject() : nullptr; +} + +ColorChooserClient* ColorInputType::GetColorChooserClient() { + return this; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h new file mode 100644 index 00000000000..d6356adc4d2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/color_input_type.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h" + +namespace blink { + +class ColorChooser; + +class ColorInputType final : public InputType, + public KeyboardClickableInputTypeView, + public ColorChooserClient { + USING_GARBAGE_COLLECTED_MIXIN(ColorInputType); + + public: + static InputType* Create(HTMLInputElement&); + ~ColorInputType() override; + void Trace(blink::Visitor*) override; + using InputType::GetElement; + + // ColorChooserClient implementation. + void DidChooseColor(const Color&) override; + void DidEndChooser() override; + Element& OwnerElement() const override; + IntRect ElementRectRelativeToViewport() const override; + Color CurrentColor() override; + bool ShouldShowSuggestions() const override; + Vector<mojom::blink::ColorSuggestionPtr> Suggestions() const override; + ColorChooserClient* GetColorChooserClient() override; + + private: + explicit ColorInputType(HTMLInputElement&); + InputTypeView* CreateView() override; + ValueMode GetValueMode() const override; + void ValueAttributeChanged() override; + void CountUsage() override; + const AtomicString& FormControlType() const override; + bool SupportsRequired() const override; + String SanitizeValue(const String&) const override; + void CreateShadowSubtree() override; + void DidSetValue(const String&, bool value_changed) override; + void HandleDOMActivateEvent(Event*) override; + void ClosePopupView() override; + bool ShouldRespectListAttribute() override; + bool TypeMismatchFor(const String&) const override; + void WarnIfValueIsInvalid(const String&) const override; + void UpdateView() override; + AXObject* PopupRootAXObject() override; + + Color ValueAsColor() const; + void EndColorChooser(); + HTMLElement* ShadowColorSwatch() const; + + Member<ColorChooser> chooser_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_COLOR_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc new file mode 100644 index 00000000000..36be3e524ae --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.cc @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_input_type.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +using blink::WebLocalizedString; +using namespace HTMLNames; + +static const int kDateDefaultStep = 1; +static const int kDateDefaultStepBase = 0; +static const int kDateStepScaleFactor = 86400000; + +inline DateInputType::DateInputType(HTMLInputElement& element) + : BaseTemporalInputType(element) {} + +InputType* DateInputType::Create(HTMLInputElement& element) { + return new DateInputType(element); +} + +void DateInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeDate); +} + +const AtomicString& DateInputType::FormControlType() const { + return InputTypeNames::date; +} + +StepRange DateInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL( + const StepRange::StepDescription, step_description, + (kDateDefaultStep, kDateDefaultStepBase, kDateStepScaleFactor, + StepRange::kParsedStepValueShouldBeInteger)); + + return InputType::CreateStepRange( + any_step_handling, kDateDefaultStepBase, + Decimal::FromDouble(DateComponents::MinimumDate()), + Decimal::FromDouble(DateComponents::MaximumDate()), step_description); +} + +bool DateInputType::ParseToDateComponentsInternal(const String& string, + DateComponents* out) const { + DCHECK(out); + unsigned end; + return out->ParseDate(string, 0, end) && end == string.length(); +} + +bool DateInputType::SetMillisecondToDateComponents(double value, + DateComponents* date) const { + DCHECK(date); + return date->SetMillisecondsSinceEpochForDate(value); +} + +void DateInputType::WarnIfValueIsInvalid(const String& value) const { + if (value != GetElement().SanitizeValue(value)) + AddWarningToConsole( + "The specified value %s does not conform to the required format, " + "\"yyyy-MM-dd\".", + value); +} + +String DateInputType::FormatDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) const { + if (!date_time_fields_state.HasDayOfMonth() || + !date_time_fields_state.HasMonth() || !date_time_fields_state.HasYear()) + return g_empty_string; + + return String::Format("%04u-%02u-%02u", date_time_fields_state.Year(), + date_time_fields_state.Month(), + date_time_fields_state.DayOfMonth()); +} + +void DateInputType::SetupLayoutParameters( + DateTimeEditElement::LayoutParameters& layout_parameters, + const DateComponents& date) const { + layout_parameters.date_time_format = layout_parameters.locale.DateFormat(); + layout_parameters.fallback_date_time_format = "yyyy-MM-dd"; + if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr), + &layout_parameters.minimum)) + layout_parameters.minimum = DateComponents(); + if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr), + &layout_parameters.maximum)) + layout_parameters.maximum = DateComponents(); + layout_parameters.placeholder_for_day = GetLocale().QueryString( + WebLocalizedString::kPlaceholderForDayOfMonthField); + layout_parameters.placeholder_for_month = + GetLocale().QueryString(WebLocalizedString::kPlaceholderForMonthField); + layout_parameters.placeholder_for_year = + GetLocale().QueryString(WebLocalizedString::kPlaceholderForYearField); +} + +bool DateInputType::IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const { + return has_year && has_month && has_day; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h new file mode 100644 index 00000000000..cbae403e3b4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_input_type.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" + +namespace blink { + +class DateInputType final : public BaseTemporalInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + explicit DateInputType(HTMLInputElement&); + + void CountUsage() override; + const AtomicString& FormControlType() const override; + StepRange CreateStepRange(AnyStepHandling) const override; + bool ParseToDateComponentsInternal(const String&, + DateComponents*) const override; + bool SetMillisecondToDateComponents(double, DateComponents*) const override; + void WarnIfValueIsInvalid(const String&) const override; + + // BaseTemporalInputType functions + String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override; + void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&, + const DateComponents&) const override; + bool IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc new file mode 100644 index 00000000000..11bffb1716d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" + +namespace blink { + +DateTimeChooser::~DateTimeChooser() = default; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h new file mode 100644 index 00000000000..e1ba1f136db --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class AXObject; + +struct DateTimeSuggestion { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + double value; + String localized_value; + String label; +}; + +struct DateTimeChooserParameters { + DISALLOW_NEW(); + AtomicString type; + IntRect anchor_rect_in_screen; + // Locale name for which the chooser should be localized. This + // might be an invalid name because it comes from HTML lang + // attributes. + AtomicString locale; + double double_value; + Vector<DateTimeSuggestion> suggestions; + double minimum; + double maximum; + double step; + double step_base; + bool required; + bool is_anchor_element_rtl; +}; + +// For pickers like color pickers and date pickers. +class CORE_EXPORT DateTimeChooser + : public GarbageCollectedFinalized<DateTimeChooser> { + public: + virtual ~DateTimeChooser(); + + virtual void EndChooser() = 0; + // Returns a root AXObject in the DateTimeChooser if it's available. + virtual AXObject* RootAXObject() = 0; + + virtual void Trace(blink::Visitor* visitor) {} +}; + +} // namespace blink +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc new file mode 100644 index 00000000000..9dd1679f8d8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" + +namespace blink { + +DateTimeChooserClient::~DateTimeChooserClient() = default; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h new file mode 100644 index 00000000000..5c4478cd04a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_client.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_CLIENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_CLIENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class Element; + +class CORE_EXPORT DateTimeChooserClient : public GarbageCollectedMixin { + public: + virtual ~DateTimeChooserClient(); + void Trace(blink::Visitor* visitor) override {} + + virtual Element& OwnerElement() const = 0; + // Called when user picked a value. + virtual void DidChooseValue(const String&) = 0; + // Called when user picked a value. + virtual void DidChooseValue(double) = 0; + // Called when chooser has ended. + virtual void DidEndChooser() = 0; +}; + +} // namespace blink +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_CLIENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc new file mode 100644 index 00000000000..d4127eaea68 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h" + +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page_popup.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/language.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +DateTimeChooserImpl::DateTimeChooserImpl( + ChromeClient* chrome_client, + DateTimeChooserClient* client, + const DateTimeChooserParameters& parameters) + : chrome_client_(chrome_client), + client_(client), + popup_(nullptr), + parameters_(parameters), + locale_(Locale::Create(parameters.locale)) { + DCHECK(RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled()); + DCHECK(chrome_client_); + DCHECK(client_); + popup_ = chrome_client_->OpenPagePopup(this); +} + +DateTimeChooserImpl* DateTimeChooserImpl::Create( + ChromeClient* chrome_client, + DateTimeChooserClient* client, + const DateTimeChooserParameters& parameters) { + return new DateTimeChooserImpl(chrome_client, client, parameters); +} + +DateTimeChooserImpl::~DateTimeChooserImpl() = default; + +void DateTimeChooserImpl::Trace(blink::Visitor* visitor) { + visitor->Trace(chrome_client_); + visitor->Trace(client_); + DateTimeChooser::Trace(visitor); +} + +void DateTimeChooserImpl::EndChooser() { + if (!popup_) + return; + chrome_client_->ClosePagePopup(popup_); +} + +AXObject* DateTimeChooserImpl::RootAXObject() { + return popup_ ? popup_->RootAXObject() : nullptr; +} + +static String ValueToDateTimeString(double value, AtomicString type) { + DateComponents components; + if (type == InputTypeNames::date) + components.SetMillisecondsSinceEpochForDate(value); + else if (type == InputTypeNames::datetime_local) + components.SetMillisecondsSinceEpochForDateTimeLocal(value); + else if (type == InputTypeNames::month) + components.SetMonthsSinceEpoch(value); + else if (type == InputTypeNames::time) + components.SetMillisecondsSinceMidnight(value); + else if (type == InputTypeNames::week) + components.SetMillisecondsSinceEpochForWeek(value); + else + NOTREACHED(); + return components.GetType() == DateComponents::kInvalid + ? String() + : components.ToString(); +} + +void DateTimeChooserImpl::WriteDocument(SharedBuffer* data) { + String step_string = String::Number(parameters_.step); + String step_base_string = String::Number(parameters_.step_base, 11); + String today_label_string; + String other_date_label_string; + if (parameters_.type == InputTypeNames::month) { + today_label_string = + GetLocale().QueryString(WebLocalizedString::kThisMonthButtonLabel); + other_date_label_string = + GetLocale().QueryString(WebLocalizedString::kOtherMonthLabel); + } else if (parameters_.type == InputTypeNames::week) { + today_label_string = + GetLocale().QueryString(WebLocalizedString::kThisWeekButtonLabel); + other_date_label_string = + GetLocale().QueryString(WebLocalizedString::kOtherWeekLabel); + } else { + today_label_string = + GetLocale().QueryString(WebLocalizedString::kCalendarToday); + other_date_label_string = + GetLocale().QueryString(WebLocalizedString::kOtherDateLabel); + } + + AddString("<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data); + data->Append(Platform::Current()->GetDataResource("pickerCommon.css")); + data->Append(Platform::Current()->GetDataResource("pickerButton.css")); + data->Append(Platform::Current()->GetDataResource("suggestionPicker.css")); + data->Append(Platform::Current()->GetDataResource("calendarPicker.css")); + AddString( + "</style></head><body><div id=main>Loading...</div><script>\n" + "window.dialogArguments = {\n", + data); + AddProperty("anchorRectInScreen", parameters_.anchor_rect_in_screen, data); + float scale_factor = chrome_client_->WindowToViewportScalar(1.0f); + AddProperty("zoomFactor", ZoomFactor() / scale_factor, data); + AddProperty("min", + ValueToDateTimeString(parameters_.minimum, parameters_.type), + data); + AddProperty("max", + ValueToDateTimeString(parameters_.maximum, parameters_.type), + data); + AddProperty("step", step_string, data); + AddProperty("stepBase", step_base_string, data); + AddProperty("required", parameters_.required, data); + AddProperty("currentValue", + ValueToDateTimeString(parameters_.double_value, parameters_.type), + data); + AddProperty("locale", parameters_.locale.GetString(), data); + AddProperty("todayLabel", today_label_string, data); + AddProperty("clearLabel", + GetLocale().QueryString(WebLocalizedString::kCalendarClear), + data); + AddProperty("weekLabel", + GetLocale().QueryString(WebLocalizedString::kWeekNumberLabel), + data); + AddProperty( + "axShowMonthSelector", + GetLocale().QueryString(WebLocalizedString::kAXCalendarShowMonthSelector), + data); + AddProperty( + "axShowNextMonth", + GetLocale().QueryString(WebLocalizedString::kAXCalendarShowNextMonth), + data); + AddProperty( + "axShowPreviousMonth", + GetLocale().QueryString(WebLocalizedString::kAXCalendarShowPreviousMonth), + data); + AddProperty("weekStartDay", locale_->FirstDayOfWeek(), data); + AddProperty("shortMonthLabels", locale_->ShortMonthLabels(), data); + AddProperty("dayLabels", locale_->WeekDayShortLabels(), data); + AddProperty("isLocaleRTL", locale_->IsRTL(), data); + AddProperty("isRTL", parameters_.is_anchor_element_rtl, data); + AddProperty("mode", parameters_.type.GetString(), data); + if (parameters_.suggestions.size()) { + Vector<String> suggestion_values; + Vector<String> localized_suggestion_values; + Vector<String> suggestion_labels; + for (unsigned i = 0; i < parameters_.suggestions.size(); i++) { + suggestion_values.push_back(ValueToDateTimeString( + parameters_.suggestions[i].value, parameters_.type)); + localized_suggestion_values.push_back( + parameters_.suggestions[i].localized_value); + suggestion_labels.push_back(parameters_.suggestions[i].label); + } + AddProperty("suggestionValues", suggestion_values, data); + AddProperty("localizedSuggestionValues", localized_suggestion_values, data); + AddProperty("suggestionLabels", suggestion_labels, data); + AddProperty( + "inputWidth", + static_cast<unsigned>(parameters_.anchor_rect_in_screen.Width()), data); + AddProperty( + "showOtherDateEntry", + LayoutTheme::GetTheme().SupportsCalendarPicker(parameters_.type), data); + AddProperty("otherDateLabel", other_date_label_string, data); + AddProperty("suggestionHighlightColor", + LayoutTheme::GetTheme() + .ActiveListBoxSelectionBackgroundColor() + .Serialized(), + data); + AddProperty("suggestionHighlightTextColor", + LayoutTheme::GetTheme() + .ActiveListBoxSelectionForegroundColor() + .Serialized(), + data); + } + AddString("}\n", data); + + data->Append(Platform::Current()->GetDataResource("pickerCommon.js")); + data->Append(Platform::Current()->GetDataResource("suggestionPicker.js")); + data->Append(Platform::Current()->GetDataResource("calendarPicker.js")); + AddString("</script></body>\n", data); +} + +Element& DateTimeChooserImpl::OwnerElement() { + return client_->OwnerElement(); +} + +Locale& DateTimeChooserImpl::GetLocale() { + return *locale_; +} + +void DateTimeChooserImpl::SetValueAndClosePopup(int num_value, + const String& string_value) { + if (num_value >= 0) + SetValue(string_value); + EndChooser(); +} + +void DateTimeChooserImpl::SetValue(const String& value) { + client_->DidChooseValue(value); +} + +void DateTimeChooserImpl::ClosePopup() { + EndChooser(); +} + +void DateTimeChooserImpl::DidClosePopup() { + DCHECK(client_); + popup_ = nullptr; + client_->DidEndChooser(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h new file mode 100644 index 00000000000..92cae9e460f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_IMPL_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_IMPL_H_ + +#include <memory> +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/core/page/page_popup_client.h" + +namespace blink { + +class ChromeClient; +class DateTimeChooserClient; +class PagePopup; + +class CORE_EXPORT DateTimeChooserImpl final : public DateTimeChooser, + public PagePopupClient { + public: + static DateTimeChooserImpl* Create(ChromeClient*, + DateTimeChooserClient*, + const DateTimeChooserParameters&); + ~DateTimeChooserImpl() override; + + // DateTimeChooser functions: + void EndChooser() override; + AXObject* RootAXObject() override; + + void Trace(blink::Visitor*) override; + + private: + DateTimeChooserImpl(ChromeClient*, + DateTimeChooserClient*, + const DateTimeChooserParameters&); + // PagePopupClient functions: + void WriteDocument(SharedBuffer*) override; + void SelectFontsFromOwnerDocument(Document&) override {} + Locale& GetLocale() override; + void SetValueAndClosePopup(int, const String&) override; + void SetValue(const String&) override; + void ClosePopup() override; + Element& OwnerElement() override; + void DidClosePopup() override; + + Member<ChromeClient> chrome_client_; + Member<DateTimeChooserClient> client_; + PagePopup* popup_; + DateTimeChooserParameters parameters_; + std::unique_ptr<Locale> locale_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_CHOOSER_IMPL_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc new file mode 100644 index 00000000000..fd88a27f3d9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc @@ -0,0 +1,871 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_edit_element.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/html/forms/date_time_field_elements.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/fonts/font_cache.h" +#include "third_party/blink/renderer/platform/text/date_time_format.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" + +namespace blink { + +using namespace HTMLNames; + +class DateTimeEditBuilder : private DateTimeFormat::TokenHandler { + public: + // The argument objects must be alive until this object dies. + DateTimeEditBuilder(DateTimeEditElement&, + const DateTimeEditElement::LayoutParameters&, + const DateComponents&); + + bool Build(const String&); + + private: + bool NeedMillisecondField() const; + bool ShouldAMPMFieldDisabled() const; + bool ShouldDayOfMonthFieldDisabled() const; + bool ShouldHourFieldDisabled() const; + bool ShouldMillisecondFieldDisabled() const; + bool ShouldMinuteFieldDisabled() const; + bool ShouldSecondFieldDisabled() const; + bool ShouldYearFieldDisabled() const; + inline const StepRange& GetStepRange() const { + return parameters_.step_range; + } + DateTimeNumericFieldElement::Step CreateStep(double ms_per_field_unit, + double ms_per_field_size) const; + + // DateTimeFormat::TokenHandler functions. + void VisitField(DateTimeFormat::FieldType, int) final; + void VisitLiteral(const String&) final; + + DateTimeEditElement& EditElement() const; + + Member<DateTimeEditElement> edit_element_; + const DateComponents date_value_; + const DateTimeEditElement::LayoutParameters& parameters_; + DateTimeNumericFieldElement::Range day_range_; + DateTimeNumericFieldElement::Range hour23_range_; + DateTimeNumericFieldElement::Range minute_range_; + DateTimeNumericFieldElement::Range second_range_; + DateTimeNumericFieldElement::Range millisecond_range_; +}; + +DateTimeEditBuilder::DateTimeEditBuilder( + DateTimeEditElement& element, + const DateTimeEditElement::LayoutParameters& layout_parameters, + const DateComponents& date_value) + : edit_element_(&element), + date_value_(date_value), + parameters_(layout_parameters), + day_range_(1, 31), + hour23_range_(0, 23), + minute_range_(0, 59), + second_range_(0, 59), + millisecond_range_(0, 999) { + if (date_value_.GetType() == DateComponents::kDate || + date_value_.GetType() == DateComponents::kDateTimeLocal) { + if (parameters_.minimum.GetType() != DateComponents::kInvalid && + parameters_.maximum.GetType() != DateComponents::kInvalid && + parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && + parameters_.minimum.Month() == parameters_.maximum.Month() && + parameters_.minimum.MonthDay() <= parameters_.maximum.MonthDay()) { + day_range_.minimum = parameters_.minimum.MonthDay(); + day_range_.maximum = parameters_.maximum.MonthDay(); + } + } + + if (date_value_.GetType() == DateComponents::kTime || + day_range_.IsSingleton()) { + if (parameters_.minimum.GetType() != DateComponents::kInvalid && + parameters_.maximum.GetType() != DateComponents::kInvalid && + parameters_.minimum.Hour() <= parameters_.maximum.Hour()) { + hour23_range_.minimum = parameters_.minimum.Hour(); + hour23_range_.maximum = parameters_.maximum.Hour(); + } + } + + if (hour23_range_.IsSingleton() && + parameters_.minimum.Minute() <= parameters_.maximum.Minute()) { + minute_range_.minimum = parameters_.minimum.Minute(); + minute_range_.maximum = parameters_.maximum.Minute(); + } + if (minute_range_.IsSingleton() && + parameters_.minimum.Second() <= parameters_.maximum.Second()) { + second_range_.minimum = parameters_.minimum.Second(); + second_range_.maximum = parameters_.maximum.Second(); + } + if (second_range_.IsSingleton() && + parameters_.minimum.Millisecond() <= parameters_.maximum.Millisecond()) { + millisecond_range_.minimum = parameters_.minimum.Millisecond(); + millisecond_range_.maximum = parameters_.maximum.Millisecond(); + } +} + +bool DateTimeEditBuilder::Build(const String& format_string) { + EditElement().ResetFields(); + return DateTimeFormat::Parse(format_string, *this); +} + +bool DateTimeEditBuilder::NeedMillisecondField() const { + return date_value_.Millisecond() || + !GetStepRange() + .Minimum() + .Remainder(static_cast<int>(kMsPerSecond)) + .IsZero() || + !GetStepRange() + .Step() + .Remainder(static_cast<int>(kMsPerSecond)) + .IsZero(); +} + +void DateTimeEditBuilder::VisitField(DateTimeFormat::FieldType field_type, + int count) { + const int kCountForAbbreviatedMonth = 3; + const int kCountForFullMonth = 4; + const int kCountForNarrowMonth = 5; + Document& document = EditElement().GetDocument(); + + switch (field_type) { + case DateTimeFormat::kFieldTypeDayOfMonth: { + DateTimeFieldElement* field = DateTimeDayFieldElement::Create( + document, EditElement(), parameters_.placeholder_for_day, day_range_); + EditElement().AddField(field); + if (ShouldDayOfMonthFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeHour11: { + DateTimeNumericFieldElement::Step step = + CreateStep(kMsPerHour, kMsPerHour * 12); + DateTimeFieldElement* field = DateTimeHour11FieldElement::Create( + document, EditElement(), hour23_range_, step); + EditElement().AddField(field); + if (ShouldHourFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeHour12: { + DateTimeNumericFieldElement::Step step = + CreateStep(kMsPerHour, kMsPerHour * 12); + DateTimeFieldElement* field = DateTimeHour12FieldElement::Create( + document, EditElement(), hour23_range_, step); + EditElement().AddField(field); + if (ShouldHourFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeHour23: { + DateTimeNumericFieldElement::Step step = + CreateStep(kMsPerHour, kMsPerDay); + DateTimeFieldElement* field = DateTimeHour23FieldElement::Create( + document, EditElement(), hour23_range_, step); + EditElement().AddField(field); + if (ShouldHourFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeHour24: { + DateTimeNumericFieldElement::Step step = + CreateStep(kMsPerHour, kMsPerDay); + DateTimeFieldElement* field = DateTimeHour24FieldElement::Create( + document, EditElement(), hour23_range_, step); + EditElement().AddField(field); + if (ShouldHourFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeMinute: { + DateTimeNumericFieldElement::Step step = + CreateStep(kMsPerMinute, kMsPerHour); + DateTimeNumericFieldElement* field = DateTimeMinuteFieldElement::Create( + document, EditElement(), minute_range_, step); + EditElement().AddField(field); + if (ShouldMinuteFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeMonth: // Fallthrough. + case DateTimeFormat::kFieldTypeMonthStandAlone: { + int min_month = 0, max_month = 11; + if (parameters_.minimum.GetType() != DateComponents::kInvalid && + parameters_.maximum.GetType() != DateComponents::kInvalid && + parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && + parameters_.minimum.Month() <= parameters_.maximum.Month()) { + min_month = parameters_.minimum.Month(); + max_month = parameters_.maximum.Month(); + } + DateTimeFieldElement* field; + switch (count) { + case kCountForNarrowMonth: // Fallthrough. + case kCountForAbbreviatedMonth: + field = DateTimeSymbolicMonthFieldElement::Create( + document, EditElement(), + field_type == DateTimeFormat::kFieldTypeMonth + ? parameters_.locale.ShortMonthLabels() + : parameters_.locale.ShortStandAloneMonthLabels(), + min_month, max_month); + break; + case kCountForFullMonth: + field = DateTimeSymbolicMonthFieldElement::Create( + document, EditElement(), + field_type == DateTimeFormat::kFieldTypeMonth + ? parameters_.locale.MonthLabels() + : parameters_.locale.StandAloneMonthLabels(), + min_month, max_month); + break; + default: + field = DateTimeMonthFieldElement::Create( + document, EditElement(), parameters_.placeholder_for_month, + DateTimeNumericFieldElement::Range(min_month + 1, max_month + 1)); + break; + } + EditElement().AddField(field); + if (min_month == max_month && min_month == date_value_.Month() && + date_value_.GetType() != DateComponents::kMonth) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypePeriod: { + DateTimeFieldElement* field = DateTimeAMPMFieldElement::Create( + document, EditElement(), parameters_.locale.TimeAMPMLabels()); + EditElement().AddField(field); + if (ShouldAMPMFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeSecond: { + DateTimeNumericFieldElement::Step step = + CreateStep(kMsPerSecond, kMsPerMinute); + DateTimeNumericFieldElement* field = DateTimeSecondFieldElement::Create( + document, EditElement(), second_range_, step); + EditElement().AddField(field); + if (ShouldSecondFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + + if (NeedMillisecondField()) { + VisitLiteral(parameters_.locale.LocalizedDecimalSeparator()); + VisitField(DateTimeFormat::kFieldTypeFractionalSecond, 3); + } + return; + } + + case DateTimeFormat::kFieldTypeFractionalSecond: { + DateTimeNumericFieldElement::Step step = CreateStep(1, kMsPerSecond); + DateTimeNumericFieldElement* field = + DateTimeMillisecondFieldElement::Create(document, EditElement(), + millisecond_range_, step); + EditElement().AddField(field); + if (ShouldMillisecondFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + case DateTimeFormat::kFieldTypeWeekOfYear: { + DateTimeNumericFieldElement::Range range( + DateComponents::kMinimumWeekNumber, + DateComponents::kMaximumWeekNumber); + if (parameters_.minimum.GetType() != DateComponents::kInvalid && + parameters_.maximum.GetType() != DateComponents::kInvalid && + parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && + parameters_.minimum.Week() <= parameters_.maximum.Week()) { + range.minimum = parameters_.minimum.Week(); + range.maximum = parameters_.maximum.Week(); + } + EditElement().AddField( + DateTimeWeekFieldElement::Create(document, EditElement(), range)); + return; + } + + case DateTimeFormat::kFieldTypeYear: { + DateTimeYearFieldElement::Parameters year_params; + if (parameters_.minimum.GetType() == DateComponents::kInvalid) { + year_params.minimum_year = DateComponents::MinimumYear(); + year_params.min_is_specified = false; + } else { + year_params.minimum_year = parameters_.minimum.FullYear(); + year_params.min_is_specified = true; + } + if (parameters_.maximum.GetType() == DateComponents::kInvalid) { + year_params.maximum_year = DateComponents::MaximumYear(); + year_params.max_is_specified = false; + } else { + year_params.maximum_year = parameters_.maximum.FullYear(); + year_params.max_is_specified = true; + } + if (year_params.minimum_year > year_params.maximum_year) { + std::swap(year_params.minimum_year, year_params.maximum_year); + std::swap(year_params.min_is_specified, year_params.max_is_specified); + } + year_params.placeholder = parameters_.placeholder_for_year; + DateTimeFieldElement* field = DateTimeYearFieldElement::Create( + document, EditElement(), year_params); + EditElement().AddField(field); + if (ShouldYearFieldDisabled()) { + field->SetValueAsDate(date_value_); + field->SetDisabled(); + } + return; + } + + default: + return; + } +} + +bool DateTimeEditBuilder::ShouldAMPMFieldDisabled() const { + return ShouldHourFieldDisabled() || + (hour23_range_.minimum < 12 && hour23_range_.maximum < 12 && + date_value_.Hour() < 12) || + (hour23_range_.minimum >= 12 && hour23_range_.maximum >= 12 && + date_value_.Hour() >= 12); +} + +bool DateTimeEditBuilder::ShouldDayOfMonthFieldDisabled() const { + return day_range_.IsSingleton() && + day_range_.minimum == date_value_.MonthDay() && + date_value_.GetType() != DateComponents::kDate; +} + +bool DateTimeEditBuilder::ShouldHourFieldDisabled() const { + if (hour23_range_.IsSingleton() && + hour23_range_.minimum == date_value_.Hour() && + !(ShouldMinuteFieldDisabled() && ShouldSecondFieldDisabled() && + ShouldMillisecondFieldDisabled())) + return true; + + if (date_value_.GetType() == DateComponents::kTime) + return false; + DCHECK_EQ(date_value_.GetType(), DateComponents::kDateTimeLocal); + + if (ShouldDayOfMonthFieldDisabled()) { + DCHECK_EQ(parameters_.minimum.FullYear(), parameters_.maximum.FullYear()); + DCHECK_EQ(parameters_.minimum.Month(), parameters_.maximum.Month()); + return false; + } + + const Decimal decimal_ms_per_day(static_cast<int>(kMsPerDay)); + Decimal hour_part_of_minimum = + (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_day) / + static_cast<int>(kMsPerHour)) + .Floor(); + return hour_part_of_minimum == date_value_.Hour() && + GetStepRange().Step().Remainder(decimal_ms_per_day).IsZero(); +} + +bool DateTimeEditBuilder::ShouldMillisecondFieldDisabled() const { + if (millisecond_range_.IsSingleton() && + millisecond_range_.minimum == date_value_.Millisecond()) + return true; + + const Decimal decimal_ms_per_second(static_cast<int>(kMsPerSecond)); + return GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_second) == + date_value_.Millisecond() && + GetStepRange().Step().Remainder(decimal_ms_per_second).IsZero(); +} + +bool DateTimeEditBuilder::ShouldMinuteFieldDisabled() const { + if (minute_range_.IsSingleton() && + minute_range_.minimum == date_value_.Minute()) + return true; + + const Decimal decimal_ms_per_hour(static_cast<int>(kMsPerHour)); + Decimal minute_part_of_minimum = + (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_hour) / + static_cast<int>(kMsPerMinute)) + .Floor(); + return minute_part_of_minimum == date_value_.Minute() && + GetStepRange().Step().Remainder(decimal_ms_per_hour).IsZero(); +} + +bool DateTimeEditBuilder::ShouldSecondFieldDisabled() const { + if (second_range_.IsSingleton() && + second_range_.minimum == date_value_.Second()) + return true; + + const Decimal decimal_ms_per_minute(static_cast<int>(kMsPerMinute)); + Decimal second_part_of_minimum = + (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_minute) / + static_cast<int>(kMsPerSecond)) + .Floor(); + return second_part_of_minimum == date_value_.Second() && + GetStepRange().Step().Remainder(decimal_ms_per_minute).IsZero(); +} + +bool DateTimeEditBuilder::ShouldYearFieldDisabled() const { + return parameters_.minimum.GetType() != DateComponents::kInvalid && + parameters_.maximum.GetType() != DateComponents::kInvalid && + parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && + parameters_.minimum.FullYear() == date_value_.FullYear(); +} + +void DateTimeEditBuilder::VisitLiteral(const String& text) { + DEFINE_STATIC_LOCAL(AtomicString, text_pseudo_id, + ("-webkit-datetime-edit-text")); + DCHECK_GT(text.length(), 0u); + HTMLDivElement* element = HTMLDivElement::Create(EditElement().GetDocument()); + element->SetShadowPseudoId(text_pseudo_id); + if (parameters_.locale.IsRTL() && text.length()) { + WTF::Unicode::CharDirection dir = WTF::Unicode::Direction(text[0]); + if (dir == WTF::Unicode::kSegmentSeparator || + dir == WTF::Unicode::kWhiteSpaceNeutral || + dir == WTF::Unicode::kOtherNeutral) + element->AppendChild(Text::Create(EditElement().GetDocument(), + String(&kRightToLeftMarkCharacter, 1))); + } + element->AppendChild(Text::Create(EditElement().GetDocument(), text)); + EditElement().FieldsWrapperElement()->AppendChild(element); +} + +DateTimeEditElement& DateTimeEditBuilder::EditElement() const { + return *edit_element_; +} + +DateTimeNumericFieldElement::Step DateTimeEditBuilder::CreateStep( + double ms_per_field_unit, + double ms_per_field_size) const { + const Decimal ms_per_field_unit_decimal(static_cast<int>(ms_per_field_unit)); + const Decimal ms_per_field_size_decimal(static_cast<int>(ms_per_field_size)); + Decimal step_milliseconds = GetStepRange().Step(); + DCHECK(!ms_per_field_unit_decimal.IsZero()); + DCHECK(!ms_per_field_size_decimal.IsZero()); + DCHECK(!step_milliseconds.IsZero()); + + DateTimeNumericFieldElement::Step step(1, 0); + + if (step_milliseconds.Remainder(ms_per_field_size_decimal).IsZero()) + step_milliseconds = ms_per_field_size_decimal; + + if (ms_per_field_size_decimal.Remainder(step_milliseconds).IsZero() && + step_milliseconds.Remainder(ms_per_field_unit_decimal).IsZero()) { + step.step = static_cast<int>( + (step_milliseconds / ms_per_field_unit_decimal).ToDouble()); + step.step_base = static_cast<int>( + (GetStepRange().StepBase() / ms_per_field_unit_decimal) + .Floor() + .Remainder(ms_per_field_size_decimal / ms_per_field_unit_decimal) + .ToDouble()); + } + return step; +} + +// ---------------------------- + +DateTimeEditElement::EditControlOwner::~EditControlOwner() = default; + +DateTimeEditElement::DateTimeEditElement(Document& document, + EditControlOwner& edit_control_owner) + : HTMLDivElement(document), edit_control_owner_(&edit_control_owner) { + SetHasCustomStyleCallbacks(); +} + +DateTimeEditElement::~DateTimeEditElement() = default; + +void DateTimeEditElement::Trace(blink::Visitor* visitor) { + visitor->Trace(fields_); + visitor->Trace(edit_control_owner_); + HTMLDivElement::Trace(visitor); +} + +inline Element* DateTimeEditElement::FieldsWrapperElement() const { + DCHECK(firstChild()); + return ToElementOrDie(firstChild()); +} + +void DateTimeEditElement::AddField(DateTimeFieldElement* field) { + if (fields_.size() >= kMaximumNumberOfFields) + return; + fields_.push_back(field); + FieldsWrapperElement()->AppendChild(field); +} + +bool DateTimeEditElement::AnyEditableFieldsHaveValues() const { + for (const auto& field : fields_) { + if (!field->IsDisabled() && field->HasValue()) + return true; + } + return false; +} + +void DateTimeEditElement::BlurByOwner() { + if (DateTimeFieldElement* field = FocusedField()) + field->blur(); +} + +DateTimeEditElement* DateTimeEditElement::Create( + Document& document, + EditControlOwner& edit_control_owner) { + DateTimeEditElement* container = + new DateTimeEditElement(document, edit_control_owner); + container->SetShadowPseudoId(AtomicString("-webkit-datetime-edit")); + container->setAttribute(idAttr, ShadowElementNames::DateTimeEdit()); + return container; +} + +scoped_refptr<ComputedStyle> DateTimeEditElement::CustomStyleForLayoutObject() { + // FIXME: This is a kind of layout. We might want to introduce new + // layoutObject. + scoped_refptr<ComputedStyle> original_style = OriginalStyleForLayoutObject(); + scoped_refptr<ComputedStyle> style = ComputedStyle::Clone(*original_style); + float width = 0; + for (Node* child = FieldsWrapperElement()->firstChild(); child; + child = child->nextSibling()) { + if (!child->IsElementNode()) + continue; + Element* child_element = ToElement(child); + if (child_element->IsDateTimeFieldElement()) { + // We need to pass the ComputedStyle of this element because child + // elements can't resolve inherited style at this timing. + width += static_cast<DateTimeFieldElement*>(child_element) + ->MaximumWidth(*style); + } else { + // ::-webkit-datetime-edit-text case. It has no + // border/padding/margin in html.css. + width += DateTimeFieldElement::ComputeTextWidth( + *style, child_element->textContent()); + } + } + style->SetWidth(Length(ceilf(width), kFixed)); + style->SetUnique(); + return style; +} + +void DateTimeEditElement::DidBlurFromField(WebFocusType focus_type) { + if (edit_control_owner_) + edit_control_owner_->DidBlurFromControl(focus_type); +} + +void DateTimeEditElement::DidFocusOnField(WebFocusType focus_type) { + if (edit_control_owner_) + edit_control_owner_->DidFocusOnControl(focus_type); +} + +void DateTimeEditElement::DisabledStateChanged() { + UpdateUIState(); +} + +DateTimeFieldElement* DateTimeEditElement::FieldAt(size_t field_index) const { + return field_index < fields_.size() ? fields_[field_index].Get() : nullptr; +} + +size_t DateTimeEditElement::FieldIndexOf( + const DateTimeFieldElement& field) const { + for (size_t field_index = 0; field_index < fields_.size(); ++field_index) { + if (fields_[field_index] == &field) + return field_index; + } + return kInvalidFieldIndex; +} + +void DateTimeEditElement::FocusIfNoFocus() { + if (FocusedFieldIndex() != kInvalidFieldIndex) + return; + FocusOnNextFocusableField(0); +} + +void DateTimeEditElement::FocusByOwner(Element* old_focused_element) { + if (old_focused_element && old_focused_element->IsDateTimeFieldElement()) { + DateTimeFieldElement* old_focused_field = + static_cast<DateTimeFieldElement*>(old_focused_element); + size_t index = FieldIndexOf(*old_focused_field); + GetDocument().UpdateStyleAndLayoutTreeForNode(old_focused_field); + if (index != kInvalidFieldIndex && old_focused_field->IsFocusable()) { + old_focused_field->focus(); + return; + } + } + FocusOnNextFocusableField(0); +} + +DateTimeFieldElement* DateTimeEditElement::FocusedField() const { + return FieldAt(FocusedFieldIndex()); +} + +size_t DateTimeEditElement::FocusedFieldIndex() const { + Element* const focused_field_element = GetDocument().FocusedElement(); + for (size_t field_index = 0; field_index < fields_.size(); ++field_index) { + if (fields_[field_index] == focused_field_element) + return field_index; + } + return kInvalidFieldIndex; +} + +void DateTimeEditElement::FieldValueChanged() { + if (edit_control_owner_) + edit_control_owner_->EditControlValueChanged(); +} + +bool DateTimeEditElement::FocusOnNextFocusableField(size_t start_index) { + GetDocument().UpdateStyleAndLayoutTreeIgnorePendingStylesheets(); + for (size_t field_index = start_index; field_index < fields_.size(); + ++field_index) { + if (fields_[field_index]->IsFocusable()) { + fields_[field_index]->focus(); + return true; + } + } + return false; +} + +bool DateTimeEditElement::FocusOnNextField(const DateTimeFieldElement& field) { + const size_t start_field_index = FieldIndexOf(field); + if (start_field_index == kInvalidFieldIndex) + return false; + return FocusOnNextFocusableField(start_field_index + 1); +} + +bool DateTimeEditElement::FocusOnPreviousField( + const DateTimeFieldElement& field) { + const size_t start_field_index = FieldIndexOf(field); + if (start_field_index == kInvalidFieldIndex) + return false; + GetDocument().UpdateStyleAndLayoutTreeIgnorePendingStylesheets(); + size_t field_index = start_field_index; + while (field_index > 0) { + --field_index; + if (fields_[field_index]->IsFocusable()) { + fields_[field_index]->focus(); + return true; + } + } + return false; +} + +bool DateTimeEditElement::IsDateTimeEditElement() const { + return true; +} + +bool DateTimeEditElement::IsDisabled() const { + return edit_control_owner_ && + edit_control_owner_->IsEditControlOwnerDisabled(); +} + +bool DateTimeEditElement::IsFieldOwnerDisabled() const { + return IsDisabled(); +} + +bool DateTimeEditElement::IsFieldOwnerReadOnly() const { + return IsReadOnly(); +} + +bool DateTimeEditElement::IsReadOnly() const { + return edit_control_owner_ && + edit_control_owner_->IsEditControlOwnerReadOnly(); +} + +void DateTimeEditElement::GetLayout(const LayoutParameters& layout_parameters, + const DateComponents& date_value) { + // TODO(tkent): We assume this function never dispatches events. However this + // can dispatch 'blur' event in Node::removeChild(). + + DEFINE_STATIC_LOCAL(AtomicString, fields_wrapper_pseudo_id, + ("-webkit-datetime-edit-fields-wrapper")); + if (!HasChildren()) { + HTMLDivElement* element = HTMLDivElement::Create(GetDocument()); + element->SetShadowPseudoId(fields_wrapper_pseudo_id); + AppendChild(element); + } + Element* fields_wrapper = FieldsWrapperElement(); + + size_t focused_field_index = FocusedFieldIndex(); + DateTimeFieldElement* const focused_field = FieldAt(focused_field_index); + const AtomicString focused_field_id = + focused_field ? focused_field->ShadowPseudoId() : g_null_atom; + + DateTimeEditBuilder builder(*this, layout_parameters, date_value); + Node* last_child_to_be_removed = fields_wrapper->lastChild(); + if (!builder.Build(layout_parameters.date_time_format) || fields_.IsEmpty()) { + last_child_to_be_removed = fields_wrapper->lastChild(); + builder.Build(layout_parameters.fallback_date_time_format); + } + + if (focused_field_index != kInvalidFieldIndex) { + for (size_t field_index = 0; field_index < fields_.size(); ++field_index) { + if (fields_[field_index]->ShadowPseudoId() == focused_field_id) { + focused_field_index = field_index; + break; + } + } + if (DateTimeFieldElement* field = + FieldAt(std::min(focused_field_index, fields_.size() - 1))) + field->focus(); + } + + if (last_child_to_be_removed) { + for (Node* child_node = fields_wrapper->firstChild(); child_node; + child_node = fields_wrapper->firstChild()) { + fields_wrapper->RemoveChild(child_node); + if (child_node == last_child_to_be_removed) + break; + } + SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControl)); + } +} + +AtomicString DateTimeEditElement::LocaleIdentifier() const { + return edit_control_owner_ ? edit_control_owner_->LocaleIdentifier() + : g_null_atom; +} + +void DateTimeEditElement::FieldDidChangeValueByKeyboard() { + if (edit_control_owner_) + edit_control_owner_->EditControlDidChangeValueByKeyboard(); +} + +void DateTimeEditElement::ReadOnlyStateChanged() { + UpdateUIState(); +} + +void DateTimeEditElement::ResetFields() { + for (const auto& field : fields_) + field->RemoveEventHandler(); + fields_.Shrink(0); +} + +void DateTimeEditElement::DefaultEventHandler(Event* event) { + // In case of control owner forward event to control, e.g. DOM + // dispatchEvent method. + if (DateTimeFieldElement* field = FocusedField()) { + field->DefaultEventHandler(event); + if (event->DefaultHandled()) + return; + } + + HTMLDivElement::DefaultEventHandler(event); +} + +void DateTimeEditElement::SetValueAsDate( + const LayoutParameters& layout_parameters, + const DateComponents& date) { + GetLayout(layout_parameters, date); + for (const auto& field : fields_) + field->SetValueAsDate(date); +} + +void DateTimeEditElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + for (const auto& field : fields_) + field->SetValueAsDateTimeFieldsState(date_time_fields_state); +} + +void DateTimeEditElement::SetEmptyValue( + const LayoutParameters& layout_parameters, + const DateComponents& date_for_read_only_field) { + GetLayout(layout_parameters, date_for_read_only_field); + for (const auto& field : fields_) + field->SetEmptyValue(DateTimeFieldElement::kDispatchNoEvent); +} + +bool DateTimeEditElement::HasFocusedField() { + return FocusedFieldIndex() != kInvalidFieldIndex; +} + +void DateTimeEditElement::SetOnlyYearMonthDay(const DateComponents& date) { + DCHECK_EQ(date.GetType(), DateComponents::kDate); + + if (!edit_control_owner_) + return; + + DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState(); + date_time_fields_state.SetYear(date.FullYear()); + date_time_fields_state.SetMonth(date.Month() + 1); + date_time_fields_state.SetDayOfMonth(date.MonthDay()); + SetValueAsDateTimeFieldsState(date_time_fields_state); + edit_control_owner_->EditControlValueChanged(); +} + +void DateTimeEditElement::StepDown() { + if (DateTimeFieldElement* const field = FocusedField()) + field->StepDown(); +} + +void DateTimeEditElement::StepUp() { + if (DateTimeFieldElement* const field = FocusedField()) + field->StepUp(); +} + +void DateTimeEditElement::UpdateUIState() { + if (IsDisabled()) { + if (DateTimeFieldElement* field = FocusedField()) + field->blur(); + } +} + +String DateTimeEditElement::Value() const { + if (!edit_control_owner_) + return g_empty_string; + return edit_control_owner_->FormatDateTimeFieldsState( + ValueAsDateTimeFieldsState()); +} + +DateTimeFieldsState DateTimeEditElement::ValueAsDateTimeFieldsState() const { + DateTimeFieldsState date_time_fields_state; + for (const auto& field : fields_) + field->PopulateDateTimeFieldsState(date_time_fields_state); + return date_time_fields_state; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h new file mode 100644 index 00000000000..68bea3cff35 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_edit_element.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_EDIT_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_EDIT_ELEMENT_H_ + +#include "base/macros.h" +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/renderer/core/html/forms/date_time_field_element.h" +#include "third_party/blink/renderer/core/html/forms/step_range.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class DateTimeFieldsState; +class Locale; +class StepRange; + +// DateTimeEditElement class contains numberic field and symbolc field for +// representing date and time, such as +// - Year, Month, Day Of Month +// - Hour, Minute, Second, Millisecond, AM/PM +class DateTimeEditElement final : public HTMLDivElement, + public DateTimeFieldElement::FieldOwner { + USING_GARBAGE_COLLECTED_MIXIN(DateTimeEditElement); + + public: + // EditControlOwner implementer must call removeEditControlOwner when + // it doesn't handle event, e.g. at destruction. + class EditControlOwner : public GarbageCollectedMixin { + public: + virtual ~EditControlOwner(); + virtual void DidBlurFromControl(WebFocusType) = 0; + virtual void DidFocusOnControl(WebFocusType) = 0; + virtual void EditControlValueChanged() = 0; + virtual String FormatDateTimeFieldsState( + const DateTimeFieldsState&) const = 0; + virtual bool IsEditControlOwnerDisabled() const = 0; + virtual bool IsEditControlOwnerReadOnly() const = 0; + virtual AtomicString LocaleIdentifier() const = 0; + virtual void EditControlDidChangeValueByKeyboard() = 0; + }; + + struct LayoutParameters { + STACK_ALLOCATED(); + String date_time_format; + String fallback_date_time_format; + Locale& locale; + const StepRange step_range; + DateComponents minimum; + DateComponents maximum; + String placeholder_for_day; + String placeholder_for_month; + String placeholder_for_year; + + LayoutParameters(Locale& locale, const StepRange& step_range) + : locale(locale), step_range(step_range) {} + }; + + static DateTimeEditElement* Create(Document&, EditControlOwner&); + + ~DateTimeEditElement() override; + void Trace(blink::Visitor*) override; + + void AddField(DateTimeFieldElement*); + bool AnyEditableFieldsHaveValues() const; + void BlurByOwner(); + void DefaultEventHandler(Event*) override; + void DisabledStateChanged(); + Element* FieldsWrapperElement() const; + void FocusIfNoFocus(); + // If oldFocusedNode is one of sub-fields, focus on it. Otherwise focus on + // the first sub-field. + void FocusByOwner(Element* old_focused_element = nullptr); + bool HasFocusedField(); + void ReadOnlyStateChanged(); + void RemoveEditControlOwner() { edit_control_owner_ = nullptr; } + void ResetFields(); + void SetEmptyValue(const LayoutParameters&, + const DateComponents& date_for_read_only_field); + void SetValueAsDate(const LayoutParameters&, const DateComponents&); + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&); + void SetOnlyYearMonthDay(const DateComponents&); + void StepDown(); + void StepUp(); + String Value() const; + DateTimeFieldsState ValueAsDateTimeFieldsState() const; + + private: + static const size_t kInvalidFieldIndex = static_cast<size_t>(-1); + + // Datetime can be represent at most five fields, such as + // 1. year + // 2. month + // 3. day-of-month + // 4. hour + // 5. minute + // 6. second + // 7. millisecond + // 8. AM/PM + static const int kMaximumNumberOfFields = 8; + + DateTimeEditElement(Document&, EditControlOwner&); + + DateTimeFieldElement* FieldAt(size_t) const; + size_t FieldIndexOf(const DateTimeFieldElement&) const; + DateTimeFieldElement* FocusedField() const; + size_t FocusedFieldIndex() const; + bool FocusOnNextFocusableField(size_t start_index); + bool IsDisabled() const; + bool IsReadOnly() const; + void GetLayout(const LayoutParameters&, const DateComponents&); + void UpdateUIState(); + + // Element function. + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override; + bool IsDateTimeEditElement() const override; + + // DateTimeFieldElement::FieldOwner functions. + void DidBlurFromField(WebFocusType) override; + void DidFocusOnField(WebFocusType) override; + void FieldValueChanged() override; + bool FocusOnNextField(const DateTimeFieldElement&) override; + bool FocusOnPreviousField(const DateTimeFieldElement&) override; + bool IsFieldOwnerDisabled() const override; + bool IsFieldOwnerReadOnly() const override; + AtomicString LocaleIdentifier() const override; + void FieldDidChangeValueByKeyboard() override; + + HeapVector<Member<DateTimeFieldElement>, kMaximumNumberOfFields> fields_; + Member<EditControlOwner> edit_control_owner_; + + DISALLOW_COPY_AND_ASSIGN(DateTimeEditElement); +}; + +DEFINE_TYPE_CASTS(DateTimeEditElement, + Element, + element, + element->IsDateTimeEditElement(), + element.IsDateTimeEditElement()); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc new file mode 100644 index 00000000000..6f2bfbaf0d2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.cc @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_field_element.h" + +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/text_run_constructor.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using namespace HTMLNames; + +DateTimeFieldElement::FieldOwner::~FieldOwner() = default; + +DateTimeFieldElement::DateTimeFieldElement(Document& document, + FieldOwner& field_owner) + : HTMLSpanElement(document), field_owner_(&field_owner) {} + +void DateTimeFieldElement::Trace(blink::Visitor* visitor) { + visitor->Trace(field_owner_); + HTMLSpanElement::Trace(visitor); +} + +float DateTimeFieldElement::ComputeTextWidth(const ComputedStyle& style, + const String& text) { + return style.GetFont().Width(ConstructTextRun(style.GetFont(), text, style)); +} + +void DateTimeFieldElement::DefaultEventHandler(Event* event) { + if (event->IsKeyboardEvent()) { + KeyboardEvent* keyboard_event = ToKeyboardEvent(event); + if (!IsDisabled() && !IsFieldOwnerDisabled() && !IsFieldOwnerReadOnly()) { + HandleKeyboardEvent(keyboard_event); + if (keyboard_event->DefaultHandled()) { + if (field_owner_) + field_owner_->FieldDidChangeValueByKeyboard(); + return; + } + } + DefaultKeyboardEventHandler(keyboard_event); + if (field_owner_) + field_owner_->FieldDidChangeValueByKeyboard(); + if (keyboard_event->DefaultHandled()) + return; + } + + HTMLElement::DefaultEventHandler(event); +} + +void DateTimeFieldElement::DefaultKeyboardEventHandler( + KeyboardEvent* keyboard_event) { + if (keyboard_event->type() != EventTypeNames::keydown) + return; + + if (IsDisabled() || IsFieldOwnerDisabled()) + return; + + const String& key = keyboard_event->key(); + + if (key == "ArrowLeft") { + if (!field_owner_) + return; + // FIXME: We'd like to use FocusController::advanceFocus(FocusDirectionLeft, + // ...) but it doesn't work for shadow nodes. webkit.org/b/104650 + if (!LocaleForOwner().IsRTL() && field_owner_->FocusOnPreviousField(*this)) + keyboard_event->SetDefaultHandled(); + return; + } + + if (key == "ArrowRight") { + if (!field_owner_) + return; + // FIXME: We'd like to use + // FocusController::advanceFocus(FocusDirectionRight, ...) + // but it doesn't work for shadow nodes. webkit.org/b/104650 + if (!LocaleForOwner().IsRTL() && field_owner_->FocusOnNextField(*this)) + keyboard_event->SetDefaultHandled(); + return; + } + + if (IsFieldOwnerReadOnly()) + return; + + if (key == "ArrowDown") { + if (keyboard_event->getModifierState("Alt")) + return; + keyboard_event->SetDefaultHandled(); + StepDown(); + return; + } + + if (key == "ArrowUp") { + keyboard_event->SetDefaultHandled(); + StepUp(); + return; + } + + if (key == "Backspace" || key == "Delete") { + keyboard_event->SetDefaultHandled(); + SetEmptyValue(kDispatchEvent); + return; + } +} + +void DateTimeFieldElement::SetFocused(bool value, WebFocusType focus_type) { + if (field_owner_) { + if (value) { + field_owner_->DidFocusOnField(focus_type); + } else { + field_owner_->DidBlurFromField(focus_type); + } + } + + ContainerNode::SetFocused(value, focus_type); +} + +void DateTimeFieldElement::FocusOnNextField() { + if (!field_owner_) + return; + field_owner_->FocusOnNextField(*this); +} + +void DateTimeFieldElement::Initialize(const AtomicString& pseudo, + const String& ax_help_text, + int ax_minimum, + int ax_maximum) { + // On accessibility, DateTimeFieldElement acts like spin button. + setAttribute(roleAttr, AtomicString("spinbutton")); + setAttribute(aria_valuetextAttr, AtomicString(VisibleValue())); + setAttribute(aria_valueminAttr, AtomicString::Number(ax_minimum)); + setAttribute(aria_valuemaxAttr, AtomicString::Number(ax_maximum)); + + setAttribute(aria_helpAttr, AtomicString(ax_help_text)); + SetShadowPseudoId(pseudo); + AppendChild(Text::Create(GetDocument(), VisibleValue())); +} + +bool DateTimeFieldElement::IsDateTimeFieldElement() const { + return true; +} + +bool DateTimeFieldElement::IsFieldOwnerDisabled() const { + return field_owner_ && field_owner_->IsFieldOwnerDisabled(); +} + +bool DateTimeFieldElement::IsFieldOwnerReadOnly() const { + return field_owner_ && field_owner_->IsFieldOwnerReadOnly(); +} + +bool DateTimeFieldElement::IsDisabled() const { + return FastHasAttribute(disabledAttr); +} + +Locale& DateTimeFieldElement::LocaleForOwner() const { + return GetDocument().GetCachedLocale(LocaleIdentifier()); +} + +AtomicString DateTimeFieldElement::LocaleIdentifier() const { + return field_owner_ ? field_owner_->LocaleIdentifier() : g_null_atom; +} + +float DateTimeFieldElement::MaximumWidth(const ComputedStyle&) { + const float kPaddingLeftAndRight = 2; // This should match to html.css. + return kPaddingLeftAndRight; +} + +void DateTimeFieldElement::SetDisabled() { + // Set HTML attribute disabled to change apperance. + SetBooleanAttribute(disabledAttr, true); + SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::CreateWithExtraData( + StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_disabled)); +} + +bool DateTimeFieldElement::SupportsFocus() const { + return !IsDisabled() && !IsFieldOwnerDisabled(); +} + +void DateTimeFieldElement::UpdateVisibleValue(EventBehavior event_behavior) { + Text* const text_node = ToText(firstChild()); + const String new_visible_value = VisibleValue(); + DCHECK_GT(new_visible_value.length(), 0u); + + if (text_node->wholeText() == new_visible_value) + return; + + text_node->ReplaceWholeText(new_visible_value); + if (HasValue()) { + setAttribute(aria_valuenowAttr, + AtomicString::Number(ValueForARIAValueNow())); + } else { + removeAttribute(aria_valuenowAttr); + } + setAttribute(aria_valuetextAttr, AtomicString(new_visible_value)); + + if (event_behavior == kDispatchEvent && field_owner_) + field_owner_->FieldValueChanged(); +} + +int DateTimeFieldElement::ValueForARIAValueNow() const { + return ValueAsInteger(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h new file mode 100644 index 00000000000..94e41be73c7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_element.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENT_H_ + +#include "base/macros.h" +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/html/html_span_element.h" + +namespace blink { + +class DateComponents; +class DateTimeFieldsState; + +// DateTimeFieldElement is base class of date time field element. +class DateTimeFieldElement : public HTMLSpanElement { + public: + enum EventBehavior { + kDispatchNoEvent, + kDispatchEvent, + }; + + // FieldOwner implementer must call removeEventHandler when + // it doesn't handle event, e.g. at destruction. + class FieldOwner : public GarbageCollectedMixin { + public: + virtual ~FieldOwner(); + virtual void DidBlurFromField(WebFocusType) = 0; + virtual void DidFocusOnField(WebFocusType) = 0; + virtual void FieldValueChanged() = 0; + virtual bool FocusOnNextField(const DateTimeFieldElement&) = 0; + virtual bool FocusOnPreviousField(const DateTimeFieldElement&) = 0; + virtual bool IsFieldOwnerDisabled() const = 0; + virtual bool IsFieldOwnerReadOnly() const = 0; + virtual AtomicString LocaleIdentifier() const = 0; + virtual void FieldDidChangeValueByKeyboard() = 0; + }; + + void DefaultEventHandler(Event*) override; + virtual bool HasValue() const = 0; + bool IsDisabled() const; + virtual float MaximumWidth(const ComputedStyle&); + virtual void PopulateDateTimeFieldsState(DateTimeFieldsState&) = 0; + void RemoveEventHandler() { field_owner_ = nullptr; } + void SetDisabled(); + virtual void SetEmptyValue(EventBehavior = kDispatchNoEvent) = 0; + virtual void SetValueAsDate(const DateComponents&) = 0; + virtual void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) = 0; + virtual void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) = 0; + virtual void StepDown() = 0; + virtual void StepUp() = 0; + virtual String Value() const = 0; + virtual String VisibleValue() const = 0; + void Trace(blink::Visitor*) override; + + static float ComputeTextWidth(const ComputedStyle&, const String&); + + protected: + DateTimeFieldElement(Document&, FieldOwner&); + void FocusOnNextField(); + virtual void HandleKeyboardEvent(KeyboardEvent*) = 0; + void Initialize(const AtomicString& pseudo, + const String& ax_help_text, + int ax_minimum, + int ax_maximum); + Locale& LocaleForOwner() const; + AtomicString LocaleIdentifier() const; + void UpdateVisibleValue(EventBehavior); + virtual int ValueAsInteger() const = 0; + virtual int ValueForARIAValueNow() const; + + // Node functions. + void SetFocused(bool, WebFocusType) override; + + private: + void DefaultKeyboardEventHandler(KeyboardEvent*); + bool IsDateTimeFieldElement() const final; + bool IsFieldOwnerDisabled() const; + bool IsFieldOwnerReadOnly() const; + bool SupportsFocus() const final; + + Member<FieldOwner> field_owner_; + + DISALLOW_COPY_AND_ASSIGN(DateTimeFieldElement); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc new file mode 100644 index 00000000000..bfb4b4b71ab --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.cc @@ -0,0 +1,794 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_field_elements.h" + +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +using blink::WebLocalizedString; + +static String QueryString(WebLocalizedString::Name name) { + return Locale::DefaultLocale().QueryString(name); +} + +DateTimeAMPMFieldElement::DateTimeAMPMFieldElement( + Document& document, + FieldOwner& field_owner, + const Vector<String>& ampm_labels) + : DateTimeSymbolicFieldElement(document, field_owner, ampm_labels, 0, 1) {} + +DateTimeAMPMFieldElement* DateTimeAMPMFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Vector<String>& ampm_labels) { + DEFINE_STATIC_LOCAL(AtomicString, ampm_pseudo_id, + ("-webkit-datetime-edit-ampm-field")); + DateTimeAMPMFieldElement* field = + new DateTimeAMPMFieldElement(document, field_owner, ampm_labels); + field->Initialize(ampm_pseudo_id, + QueryString(WebLocalizedString::kAXAMPMFieldText)); + return field; +} + +void DateTimeAMPMFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + if (HasValue()) + date_time_fields_state.SetAMPM(ValueAsInteger() + ? DateTimeFieldsState::kAMPMValuePM + : DateTimeFieldsState::kAMPMValueAM); + else + date_time_fields_state.SetAMPM(DateTimeFieldsState::kAMPMValueEmpty); +} + +void DateTimeAMPMFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.Hour() >= 12 ? 1 : 0); +} + +void DateTimeAMPMFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (date_time_fields_state.HasAMPM()) + SetValueAsInteger(date_time_fields_state.Ampm()); + else + SetEmptyValue(); +} + +// ---------------------------- + +DateTimeDayFieldElement::DateTimeDayFieldElement(Document& document, + FieldOwner& field_owner, + const String& placeholder, + const Range& range) + : DateTimeNumericFieldElement(document, + field_owner, + range, + Range(1, 31), + placeholder) {} + +DateTimeDayFieldElement* DateTimeDayFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const String& placeholder, + const Range& range) { + DEFINE_STATIC_LOCAL(AtomicString, day_pseudo_id, + ("-webkit-datetime-edit-day-field")); + DateTimeDayFieldElement* field = new DateTimeDayFieldElement( + document, field_owner, placeholder.IsEmpty() ? "--" : placeholder, range); + field->Initialize(day_pseudo_id, + QueryString(WebLocalizedString::kAXDayOfMonthFieldText)); + return field; +} + +void DateTimeDayFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetDayOfMonth( + HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeDayFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.MonthDay()); +} + +void DateTimeDayFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasDayOfMonth()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.DayOfMonth(); + if (GetRange().IsInRange(static_cast<int>(value))) { + SetValueAsInteger(value); + return; + } + + SetEmptyValue(); +} + +// ---------------------------- + +DateTimeHourFieldElementBase::DateTimeHourFieldElementBase( + Document& document, + FieldOwner& field_owner, + const Range& range, + const Range& hard_limits, + const Step& step) + : DateTimeNumericFieldElement(document, + field_owner, + range, + hard_limits, + "--", + step) {} + +void DateTimeHourFieldElementBase::Initialize() { + DEFINE_STATIC_LOCAL(AtomicString, hour_pseudo_id, + ("-webkit-datetime-edit-hour-field")); + DateTimeNumericFieldElement::Initialize( + hour_pseudo_id, QueryString(WebLocalizedString::kAXHourFieldText)); +} + +void DateTimeHourFieldElementBase::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.Hour()); +} + +void DateTimeHourFieldElementBase::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasHour()) { + SetEmptyValue(); + return; + } + + const int hour12 = date_time_fields_state.Hour(); + if (hour12 < 1 || hour12 > 12) { + SetEmptyValue(); + return; + } + + const int hour11 = hour12 == 12 ? 0 : hour12; + const int hour23 = + date_time_fields_state.Ampm() == DateTimeFieldsState::kAMPMValuePM + ? hour11 + 12 + : hour11; + SetValueAsInteger(hour23); +} +// ---------------------------- + +DateTimeHour11FieldElement::DateTimeHour11FieldElement(Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeHourFieldElementBase(document, + field_owner, + range, + Range(0, 11), + step) {} + +DateTimeHour11FieldElement* DateTimeHour11FieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& hour23_range, + const Step& step) { + DCHECK_GE(hour23_range.minimum, 0); + DCHECK_LE(hour23_range.maximum, 23); + DCHECK_LE(hour23_range.minimum, hour23_range.maximum); + Range range(0, 11); + if (hour23_range.maximum < 12) { + range = hour23_range; + } else if (hour23_range.minimum >= 12) { + range.minimum = hour23_range.minimum - 12; + range.maximum = hour23_range.maximum - 12; + } + + DateTimeHour11FieldElement* field = + new DateTimeHour11FieldElement(document, field_owner, range, step); + field->Initialize(); + return field; +} + +void DateTimeHour11FieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + if (!HasValue()) { + date_time_fields_state.SetHour(DateTimeFieldsState::kEmptyValue); + return; + } + const int value = ValueAsInteger(); + date_time_fields_state.SetHour(value ? value : 12); +} + +void DateTimeHour11FieldElement::SetValueAsInteger( + int value, + EventBehavior event_behavior) { + value = Range(0, 23).ClampValue(value) % 12; + DateTimeNumericFieldElement::SetValueAsInteger(value, event_behavior); +} + +// ---------------------------- + +DateTimeHour12FieldElement::DateTimeHour12FieldElement(Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeHourFieldElementBase(document, + field_owner, + range, + Range(1, 12), + step) {} + +DateTimeHour12FieldElement* DateTimeHour12FieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& hour23_range, + const Step& step) { + DCHECK_GE(hour23_range.minimum, 0); + DCHECK_LE(hour23_range.maximum, 23); + DCHECK_LE(hour23_range.minimum, hour23_range.maximum); + Range range(1, 12); + if (hour23_range.maximum < 12) { + range = hour23_range; + } else if (hour23_range.minimum >= 12) { + range.minimum = hour23_range.minimum - 12; + range.maximum = hour23_range.maximum - 12; + } + if (!range.minimum) + range.minimum = 12; + if (!range.maximum) + range.maximum = 12; + if (range.minimum > range.maximum) { + range.minimum = 1; + range.maximum = 12; + } + DateTimeHour12FieldElement* field = + new DateTimeHour12FieldElement(document, field_owner, range, step); + field->Initialize(); + return field; +} + +void DateTimeHour12FieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetHour(HasValue() ? ValueAsInteger() + : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeHour12FieldElement::SetValueAsInteger( + int value, + EventBehavior event_behavior) { + value = Range(0, 24).ClampValue(value) % 12; + DateTimeNumericFieldElement::SetValueAsInteger(value ? value : 12, + event_behavior); +} + +// ---------------------------- + +DateTimeHour23FieldElement::DateTimeHour23FieldElement(Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeHourFieldElementBase(document, + field_owner, + range, + Range(0, 23), + step) {} + +DateTimeHour23FieldElement* DateTimeHour23FieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& hour23_range, + const Step& step) { + DCHECK_GE(hour23_range.minimum, 0); + DCHECK_LE(hour23_range.maximum, 23); + DCHECK_LE(hour23_range.minimum, hour23_range.maximum); + DateTimeHour23FieldElement* field = + new DateTimeHour23FieldElement(document, field_owner, hour23_range, step); + field->Initialize(); + return field; +} + +void DateTimeHour23FieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + if (!HasValue()) { + date_time_fields_state.SetHour(DateTimeFieldsState::kEmptyValue); + return; + } + + const int value = ValueAsInteger(); + + date_time_fields_state.SetHour(value % 12 ? value % 12 : 12); + date_time_fields_state.SetAMPM(value >= 12 + ? DateTimeFieldsState::kAMPMValuePM + : DateTimeFieldsState::kAMPMValueAM); +} + +void DateTimeHour23FieldElement::SetValueAsInteger( + int value, + EventBehavior event_behavior) { + value = Range(0, 23).ClampValue(value); + DateTimeNumericFieldElement::SetValueAsInteger(value, event_behavior); +} + +// ---------------------------- + +DateTimeHour24FieldElement::DateTimeHour24FieldElement(Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeHourFieldElementBase(document, + field_owner, + range, + Range(1, 24), + step) {} + +DateTimeHour24FieldElement* DateTimeHour24FieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& hour23_range, + const Step& step) { + DCHECK_GE(hour23_range.minimum, 0); + DCHECK_LE(hour23_range.maximum, 23); + DCHECK_LE(hour23_range.minimum, hour23_range.maximum); + Range range(hour23_range.minimum ? hour23_range.minimum : 24, + hour23_range.maximum ? hour23_range.maximum : 24); + if (range.minimum > range.maximum) { + range.minimum = 1; + range.maximum = 24; + } + + DateTimeHour24FieldElement* field = + new DateTimeHour24FieldElement(document, field_owner, range, step); + field->Initialize(); + return field; +} + +void DateTimeHour24FieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + if (!HasValue()) { + date_time_fields_state.SetHour(DateTimeFieldsState::kEmptyValue); + return; + } + + const int value = ValueAsInteger(); + + if (value == 24) { + date_time_fields_state.SetHour(12); + date_time_fields_state.SetAMPM(DateTimeFieldsState::kAMPMValueAM); + } else { + date_time_fields_state.SetHour(value == 12 ? 12 : value % 12); + date_time_fields_state.SetAMPM(value >= 12 + ? DateTimeFieldsState::kAMPMValuePM + : DateTimeFieldsState::kAMPMValueAM); + } +} + +void DateTimeHour24FieldElement::SetValueAsInteger( + int value, + EventBehavior event_behavior) { + value = Range(0, 24).ClampValue(value); + DateTimeNumericFieldElement::SetValueAsInteger(value ? value : 24, + event_behavior); +} + +// ---------------------------- + +DateTimeMillisecondFieldElement::DateTimeMillisecondFieldElement( + Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeNumericFieldElement(document, + field_owner, + range, + Range(0, 999), + "---", + step) {} + +DateTimeMillisecondFieldElement* DateTimeMillisecondFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) { + DEFINE_STATIC_LOCAL(AtomicString, millisecond_pseudo_id, + ("-webkit-datetime-edit-millisecond-field")); + DateTimeMillisecondFieldElement* field = + new DateTimeMillisecondFieldElement(document, field_owner, range, step); + field->Initialize(millisecond_pseudo_id, + QueryString(WebLocalizedString::kAXMillisecondFieldText)); + return field; +} + +void DateTimeMillisecondFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetMillisecond( + HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeMillisecondFieldElement::SetValueAsDate( + const DateComponents& date) { + SetValueAsInteger(date.Millisecond()); +} + +void DateTimeMillisecondFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasMillisecond()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.Millisecond(); + if (value > static_cast<unsigned>(Maximum())) { + SetEmptyValue(); + return; + } + + SetValueAsInteger(value); +} + +// ---------------------------- + +DateTimeMinuteFieldElement::DateTimeMinuteFieldElement(Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeNumericFieldElement(document, + field_owner, + range, + Range(0, 59), + "--", + step) {} + +DateTimeMinuteFieldElement* DateTimeMinuteFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) { + DEFINE_STATIC_LOCAL(AtomicString, minute_pseudo_id, + ("-webkit-datetime-edit-minute-field")); + DateTimeMinuteFieldElement* field = + new DateTimeMinuteFieldElement(document, field_owner, range, step); + field->Initialize(minute_pseudo_id, + QueryString(WebLocalizedString::kAXMinuteFieldText)); + return field; +} + +void DateTimeMinuteFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetMinute( + HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeMinuteFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.Minute()); +} + +void DateTimeMinuteFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasMinute()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.Minute(); + if (value > static_cast<unsigned>(Maximum())) { + SetEmptyValue(); + return; + } + + SetValueAsInteger(value); +} + +// ---------------------------- + +DateTimeMonthFieldElement::DateTimeMonthFieldElement(Document& document, + FieldOwner& field_owner, + const String& placeholder, + const Range& range) + : DateTimeNumericFieldElement(document, + field_owner, + range, + Range(1, 12), + placeholder) {} + +DateTimeMonthFieldElement* DateTimeMonthFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const String& placeholder, + const Range& range) { + DEFINE_STATIC_LOCAL(AtomicString, month_pseudo_id, + ("-webkit-datetime-edit-month-field")); + DateTimeMonthFieldElement* field = new DateTimeMonthFieldElement( + document, field_owner, placeholder.IsEmpty() ? "--" : placeholder, range); + field->Initialize(month_pseudo_id, + QueryString(WebLocalizedString::kAXMonthFieldText)); + return field; +} + +void DateTimeMonthFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetMonth( + HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeMonthFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.Month() + 1); +} + +void DateTimeMonthFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasMonth()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.Month(); + if (GetRange().IsInRange(static_cast<int>(value))) { + SetValueAsInteger(value); + return; + } + + SetEmptyValue(); +} + +// ---------------------------- + +DateTimeSecondFieldElement::DateTimeSecondFieldElement(Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) + : DateTimeNumericFieldElement(document, + field_owner, + range, + Range(0, 59), + "--", + step) {} + +DateTimeSecondFieldElement* DateTimeSecondFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& range, + const Step& step) { + DEFINE_STATIC_LOCAL(AtomicString, second_pseudo_id, + ("-webkit-datetime-edit-second-field")); + DateTimeSecondFieldElement* field = + new DateTimeSecondFieldElement(document, field_owner, range, step); + field->Initialize(second_pseudo_id, + QueryString(WebLocalizedString::kAXSecondFieldText)); + return field; +} + +void DateTimeSecondFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetSecond( + HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeSecondFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.Second()); +} + +void DateTimeSecondFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasSecond()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.Second(); + if (value > static_cast<unsigned>(Maximum())) { + SetEmptyValue(); + return; + } + + SetValueAsInteger(value); +} + +// ---------------------------- + +DateTimeSymbolicMonthFieldElement::DateTimeSymbolicMonthFieldElement( + Document& document, + FieldOwner& field_owner, + const Vector<String>& labels, + int minimum, + int maximum) + : DateTimeSymbolicFieldElement(document, + field_owner, + labels, + minimum, + maximum) {} + +DateTimeSymbolicMonthFieldElement* DateTimeSymbolicMonthFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Vector<String>& labels, + int minimum, + int maximum) { + DEFINE_STATIC_LOCAL(AtomicString, month_pseudo_id, + ("-webkit-datetime-edit-month-field")); + DateTimeSymbolicMonthFieldElement* field = + new DateTimeSymbolicMonthFieldElement(document, field_owner, labels, + minimum, maximum); + field->Initialize(month_pseudo_id, + QueryString(WebLocalizedString::kAXMonthFieldText)); + return field; +} + +void DateTimeSymbolicMonthFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + if (!HasValue()) + date_time_fields_state.SetMonth(DateTimeFieldsState::kEmptyValue); + DCHECK_LT(ValueAsInteger(), static_cast<int>(SymbolsSize())); + date_time_fields_state.SetMonth(ValueAsInteger() + 1); +} + +void DateTimeSymbolicMonthFieldElement::SetValueAsDate( + const DateComponents& date) { + SetValueAsInteger(date.Month()); +} + +void DateTimeSymbolicMonthFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasMonth()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.Month() - 1; + if (value >= SymbolsSize()) { + SetEmptyValue(); + return; + } + + SetValueAsInteger(value); +} + +// ---------------------------- + +DateTimeWeekFieldElement::DateTimeWeekFieldElement(Document& document, + FieldOwner& field_owner, + const Range& range) + : DateTimeNumericFieldElement(document, + field_owner, + range, + Range(DateComponents::kMinimumWeekNumber, + DateComponents::kMaximumWeekNumber), + "--") {} + +DateTimeWeekFieldElement* DateTimeWeekFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const Range& range) { + DEFINE_STATIC_LOCAL(AtomicString, week_pseudo_id, + ("-webkit-datetime-edit-week-field")); + DateTimeWeekFieldElement* field = + new DateTimeWeekFieldElement(document, field_owner, range); + field->Initialize(week_pseudo_id, + QueryString(WebLocalizedString::kAXWeekOfYearFieldText)); + return field; +} + +void DateTimeWeekFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetWeekOfYear( + HasValue() ? ValueAsInteger() : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeWeekFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.Week()); +} + +void DateTimeWeekFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasWeekOfYear()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.WeekOfYear(); + if (GetRange().IsInRange(static_cast<int>(value))) { + SetValueAsInteger(value); + return; + } + + SetEmptyValue(); +} + +// ---------------------------- + +DateTimeYearFieldElement::DateTimeYearFieldElement( + Document& document, + FieldOwner& field_owner, + const DateTimeYearFieldElement::Parameters& parameters) + : DateTimeNumericFieldElement( + document, + field_owner, + Range(parameters.minimum_year, parameters.maximum_year), + Range(DateComponents::MinimumYear(), DateComponents::MaximumYear()), + parameters.placeholder.IsEmpty() ? "----" : parameters.placeholder), + min_is_specified_(parameters.min_is_specified), + max_is_specified_(parameters.max_is_specified) { + DCHECK_GE(parameters.minimum_year, DateComponents::MinimumYear()); + DCHECK_LE(parameters.maximum_year, DateComponents::MaximumYear()); +} + +DateTimeYearFieldElement* DateTimeYearFieldElement::Create( + Document& document, + FieldOwner& field_owner, + const DateTimeYearFieldElement::Parameters& parameters) { + DEFINE_STATIC_LOCAL(AtomicString, year_pseudo_id, + ("-webkit-datetime-edit-year-field")); + DateTimeYearFieldElement* field = + new DateTimeYearFieldElement(document, field_owner, parameters); + field->Initialize(year_pseudo_id, + QueryString(WebLocalizedString::kAXYearFieldText)); + return field; +} + +static int CurrentFullYear() { + DateComponents date; + date.SetMillisecondsSinceEpochForMonth(ConvertToLocalTime(CurrentTimeMS())); + return date.FullYear(); +} + +int DateTimeYearFieldElement::DefaultValueForStepDown() const { + return max_is_specified_ + ? DateTimeNumericFieldElement::DefaultValueForStepDown() + : CurrentFullYear(); +} + +int DateTimeYearFieldElement::DefaultValueForStepUp() const { + return min_is_specified_ + ? DateTimeNumericFieldElement::DefaultValueForStepUp() + : CurrentFullYear(); +} + +void DateTimeYearFieldElement::PopulateDateTimeFieldsState( + DateTimeFieldsState& date_time_fields_state) { + date_time_fields_state.SetYear(HasValue() ? ValueAsInteger() + : DateTimeFieldsState::kEmptyValue); +} + +void DateTimeYearFieldElement::SetValueAsDate(const DateComponents& date) { + SetValueAsInteger(date.FullYear()); +} + +void DateTimeYearFieldElement::SetValueAsDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) { + if (!date_time_fields_state.HasYear()) { + SetEmptyValue(); + return; + } + + const unsigned value = date_time_fields_state.Year(); + if (GetRange().IsInRange(static_cast<int>(value))) { + SetValueAsInteger(value); + return; + } + + SetEmptyValue(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h new file mode 100644 index 00000000000..758f9a71d56 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_field_elements.h @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENTS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELD_ELEMENTS_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h" +#include "third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class DateTimeAMPMFieldElement final : public DateTimeSymbolicFieldElement { + public: + static DateTimeAMPMFieldElement* Create(Document&, + FieldOwner&, + const Vector<String>&); + + private: + DateTimeAMPMFieldElement(Document&, FieldOwner&, const Vector<String>&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeAMPMFieldElement); +}; + +class DateTimeDayFieldElement final : public DateTimeNumericFieldElement { + public: + static DateTimeDayFieldElement* Create(Document&, + FieldOwner&, + const String& placeholder, + const Range&); + + private: + DateTimeDayFieldElement(Document&, + FieldOwner&, + const String& placeholder, + const Range&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeDayFieldElement); +}; + +class DateTimeHourFieldElementBase : public DateTimeNumericFieldElement { + protected: + DateTimeHourFieldElementBase(Document&, + FieldOwner&, + const Range&, + const Range& hard_limits, + const Step&); + void Initialize(); + + private: + // DateTimeFieldElement functions. + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeHourFieldElementBase); +}; + +class DateTimeHour11FieldElement final : public DateTimeHourFieldElementBase { + public: + static DateTimeHour11FieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeHour11FieldElement(Document&, + FieldOwner&, + const Range& hour23_range, + const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeHour11FieldElement); +}; + +class DateTimeHour12FieldElement final : public DateTimeHourFieldElementBase { + public: + static DateTimeHour12FieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeHour12FieldElement(Document&, + FieldOwner&, + const Range& hour23_range, + const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeHour12FieldElement); +}; + +class DateTimeHour23FieldElement final : public DateTimeHourFieldElementBase { + public: + static DateTimeHour23FieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeHour23FieldElement(Document&, + FieldOwner&, + const Range& hour23_range, + const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeHour23FieldElement); +}; + +class DateTimeHour24FieldElement final : public DateTimeHourFieldElementBase { + public: + static DateTimeHour24FieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeHour24FieldElement(Document&, + FieldOwner&, + const Range& hour23_range, + const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeHour24FieldElement); +}; + +class DateTimeMillisecondFieldElement final + : public DateTimeNumericFieldElement { + public: + static DateTimeMillisecondFieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeMillisecondFieldElement(Document&, + FieldOwner&, + const Range&, + const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeMillisecondFieldElement); +}; + +class DateTimeMinuteFieldElement final : public DateTimeNumericFieldElement { + public: + static DateTimeMinuteFieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeMinuteFieldElement(Document&, FieldOwner&, const Range&, const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeMinuteFieldElement); +}; + +class DateTimeMonthFieldElement final : public DateTimeNumericFieldElement { + public: + static DateTimeMonthFieldElement* Create(Document&, + FieldOwner&, + const String& placeholder, + const Range&); + + private: + DateTimeMonthFieldElement(Document&, + FieldOwner&, + const String& placeholder, + const Range&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeMonthFieldElement); +}; + +class DateTimeSecondFieldElement final : public DateTimeNumericFieldElement { + public: + static DateTimeSecondFieldElement* Create(Document&, + FieldOwner&, + const Range&, + const Step&); + + private: + DateTimeSecondFieldElement(Document&, FieldOwner&, const Range&, const Step&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeSecondFieldElement); +}; + +class DateTimeSymbolicMonthFieldElement final + : public DateTimeSymbolicFieldElement { + public: + static DateTimeSymbolicMonthFieldElement* Create(Document&, + FieldOwner&, + const Vector<String>&, + int minimum, + int maximum); + + private: + DateTimeSymbolicMonthFieldElement(Document&, + FieldOwner&, + const Vector<String>&, + int minimum, + int maximum); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeSymbolicMonthFieldElement); +}; + +class DateTimeWeekFieldElement final : public DateTimeNumericFieldElement { + public: + static DateTimeWeekFieldElement* Create(Document&, FieldOwner&, const Range&); + + private: + DateTimeWeekFieldElement(Document&, FieldOwner&, const Range&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + DISALLOW_COPY_AND_ASSIGN(DateTimeWeekFieldElement); +}; + +class DateTimeYearFieldElement final : public DateTimeNumericFieldElement { + public: + struct Parameters { + STACK_ALLOCATED(); + int minimum_year; + int maximum_year; + bool min_is_specified; + bool max_is_specified; + String placeholder; + + Parameters() + : minimum_year(-1), + maximum_year(-1), + min_is_specified(false), + max_is_specified(false) {} + }; + + static DateTimeYearFieldElement* Create(Document&, + FieldOwner&, + const Parameters&); + + private: + DateTimeYearFieldElement(Document&, FieldOwner&, const Parameters&); + + // DateTimeFieldElement functions. + void PopulateDateTimeFieldsState(DateTimeFieldsState&) override; + void SetValueAsDate(const DateComponents&) override; + void SetValueAsDateTimeFieldsState(const DateTimeFieldsState&) override; + + // DateTimeNumericFieldElement functions. + int DefaultValueForStepDown() const override; + int DefaultValueForStepUp() const override; + + bool min_is_specified_; + bool max_is_specified_; + + DISALLOW_COPY_AND_ASSIGN(DateTimeYearFieldElement); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc new file mode 100644 index 00000000000..9896a7f9d0c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.cc @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" + +#include "third_party/blink/renderer/core/html/forms/form_controller.h" + +namespace blink { + +const unsigned DateTimeFieldsState::kEmptyValue = static_cast<unsigned>(-1); + +static unsigned GetNumberFromFormControlState(const FormControlState& state, + size_t index) { + if (index >= state.ValueSize()) + return DateTimeFieldsState::kEmptyValue; + bool parsed; + unsigned const value = state[index].ToUInt(&parsed); + return parsed ? value : DateTimeFieldsState::kEmptyValue; +} + +static DateTimeFieldsState::AMPMValue GetAMPMFromFormControlState( + const FormControlState& state, + size_t index) { + if (index >= state.ValueSize()) + return DateTimeFieldsState::kAMPMValueEmpty; + const String value = state[index]; + if (value == "A") + return DateTimeFieldsState::kAMPMValueAM; + if (value == "P") + return DateTimeFieldsState::kAMPMValuePM; + return DateTimeFieldsState::kAMPMValueEmpty; +} + +DateTimeFieldsState::DateTimeFieldsState() + : year_(kEmptyValue), + month_(kEmptyValue), + day_of_month_(kEmptyValue), + hour_(kEmptyValue), + minute_(kEmptyValue), + second_(kEmptyValue), + millisecond_(kEmptyValue), + week_of_year_(kEmptyValue), + ampm_(kAMPMValueEmpty) {} + +unsigned DateTimeFieldsState::Hour23() const { + if (!HasHour() || !HasAMPM()) + return kEmptyValue; + return (hour_ % 12) + (ampm_ == kAMPMValuePM ? 12 : 0); +} + +DateTimeFieldsState DateTimeFieldsState::RestoreFormControlState( + const FormControlState& state) { + DateTimeFieldsState date_time_fields_state; + date_time_fields_state.SetYear(GetNumberFromFormControlState(state, 0)); + date_time_fields_state.SetMonth(GetNumberFromFormControlState(state, 1)); + date_time_fields_state.SetDayOfMonth(GetNumberFromFormControlState(state, 2)); + date_time_fields_state.SetHour(GetNumberFromFormControlState(state, 3)); + date_time_fields_state.SetMinute(GetNumberFromFormControlState(state, 4)); + date_time_fields_state.SetSecond(GetNumberFromFormControlState(state, 5)); + date_time_fields_state.SetMillisecond( + GetNumberFromFormControlState(state, 6)); + date_time_fields_state.SetWeekOfYear(GetNumberFromFormControlState(state, 7)); + date_time_fields_state.SetAMPM(GetAMPMFromFormControlState(state, 8)); + return date_time_fields_state; +} + +FormControlState DateTimeFieldsState::SaveFormControlState() const { + FormControlState state; + state.Append(HasYear() ? String::Number(year_) : g_empty_string); + state.Append(HasMonth() ? String::Number(month_) : g_empty_string); + state.Append(HasDayOfMonth() ? String::Number(day_of_month_) + : g_empty_string); + state.Append(HasHour() ? String::Number(hour_) : g_empty_string); + state.Append(HasMinute() ? String::Number(minute_) : g_empty_string); + state.Append(HasSecond() ? String::Number(second_) : g_empty_string); + state.Append(HasMillisecond() ? String::Number(millisecond_) + : g_empty_string); + state.Append(HasWeekOfYear() ? String::Number(week_of_year_) + : g_empty_string); + if (HasAMPM()) + state.Append(ampm_ == kAMPMValueAM ? "A" : "P"); + else + state.Append(g_empty_string); + return state; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h new file mode 100644 index 00000000000..36020b2a3ed --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_fields_state.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELDS_STATE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_FIELDS_STATE_H_ + +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class FormControlState; + +// DateTimeFieldsState represents fields in date/time for form state +// save/restore for input type "date", "datetime", "datetime-local", "month", +// "time", and "week" with multiple fields input UI. +// +// Each field can contain invalid value for date, e.g. day of month field can +// be 30 even if month field is February. +class DateTimeFieldsState { + STACK_ALLOCATED(); + + public: + enum AMPMValue { + kAMPMValueEmpty = -1, + kAMPMValueAM, + kAMPMValuePM, + }; + + static const unsigned kEmptyValue; + + DateTimeFieldsState(); + + static DateTimeFieldsState RestoreFormControlState(const FormControlState&); + FormControlState SaveFormControlState() const; + + AMPMValue Ampm() const { return ampm_; } + unsigned DayOfMonth() const { return day_of_month_; } + unsigned Hour() const { return hour_; } + unsigned Hour23() const; + unsigned Millisecond() const { return millisecond_; } + unsigned Minute() const { return minute_; } + unsigned Month() const { return month_; } + unsigned Second() const { return second_; } + unsigned WeekOfYear() const { return week_of_year_; } + unsigned Year() const { return year_; } + + bool HasAMPM() const { return ampm_ != kAMPMValueEmpty; } + bool HasDayOfMonth() const { return day_of_month_ != kEmptyValue; } + bool HasHour() const { return hour_ != kEmptyValue; } + bool HasMillisecond() const { return millisecond_ != kEmptyValue; } + bool HasMinute() const { return minute_ != kEmptyValue; } + bool HasMonth() const { return month_ != kEmptyValue; } + bool HasSecond() const { return second_ != kEmptyValue; } + bool HasWeekOfYear() const { return week_of_year_ != kEmptyValue; } + bool HasYear() const { return year_ != kEmptyValue; } + + void SetAMPM(AMPMValue ampm) { ampm_ = ampm; } + void SetDayOfMonth(unsigned day_of_month) { day_of_month_ = day_of_month; } + void SetHour(unsigned hour12) { hour_ = hour12; } + void SetMillisecond(unsigned millisecond) { millisecond_ = millisecond; } + void SetMinute(unsigned minute) { minute_ = minute; } + void SetMonth(unsigned month) { month_ = month; } + void SetSecond(unsigned second) { second_ = second; } + void SetWeekOfYear(unsigned week_of_year) { week_of_year_ = week_of_year; } + void SetYear(unsigned year) { year_ = year; } + + private: + unsigned year_; + unsigned month_; // 1 to 12. + unsigned day_of_month_; + unsigned hour_; // 1 to 12. + unsigned minute_; + unsigned second_; + unsigned millisecond_; + unsigned week_of_year_; + AMPMValue ampm_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc new file mode 100644 index 00000000000..083cd0b302f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.cc @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_local_input_type.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using blink::WebLocalizedString; +using namespace HTMLNames; + +static const int kDateTimeLocalDefaultStep = 60; +static const int kDateTimeLocalDefaultStepBase = 0; +static const int kDateTimeLocalStepScaleFactor = 1000; + +InputType* DateTimeLocalInputType::Create(HTMLInputElement& element) { + return new DateTimeLocalInputType(element); +} + +void DateTimeLocalInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeDateTimeLocal); +} + +const AtomicString& DateTimeLocalInputType::FormControlType() const { + return InputTypeNames::datetime_local; +} + +double DateTimeLocalInputType::ValueAsDate() const { + // valueAsDate doesn't work for the datetime-local type according to the + // standard. + return DateComponents::InvalidMilliseconds(); +} + +void DateTimeLocalInputType::SetValueAsDate( + double value, + ExceptionState& exception_state) const { + // valueAsDate doesn't work for the datetime-local type according to the + // standard. + InputType::SetValueAsDate(value, exception_state); +} + +StepRange DateTimeLocalInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL(const StepRange::StepDescription, step_description, + (kDateTimeLocalDefaultStep, kDateTimeLocalDefaultStepBase, + kDateTimeLocalStepScaleFactor, + StepRange::kScaledStepValueShouldBeInteger)); + + return InputType::CreateStepRange( + any_step_handling, kDateTimeLocalDefaultStepBase, + Decimal::FromDouble(DateComponents::MinimumDateTime()), + Decimal::FromDouble(DateComponents::MaximumDateTime()), step_description); +} + +bool DateTimeLocalInputType::ParseToDateComponentsInternal( + const String& string, + DateComponents* out) const { + DCHECK(out); + unsigned end; + return out->ParseDateTimeLocal(string, 0, end) && end == string.length(); +} + +bool DateTimeLocalInputType::SetMillisecondToDateComponents( + double value, + DateComponents* date) const { + DCHECK(date); + return date->SetMillisecondsSinceEpochForDateTimeLocal(value); +} + +String DateTimeLocalInputType::LocalizeValue( + const String& proposed_value) const { + DateComponents date; + if (!ParseToDateComponents(proposed_value, &date)) + return proposed_value; + + Locale::FormatType format_type = ShouldHaveSecondField(date) + ? Locale::kFormatTypeMedium + : Locale::kFormatTypeShort; + String localized = GetElement().GetLocale().FormatDateTime(date, format_type); + return localized.IsEmpty() ? proposed_value : localized; +} + +void DateTimeLocalInputType::WarnIfValueIsInvalid(const String& value) const { + if (value != GetElement().SanitizeValue(value)) + AddWarningToConsole( + "The specified value %s does not conform to the required format. The " + "format is \"yyyy-MM-ddThh:mm\" followed by optional \":ss\" or " + "\":ss.SSS\".", + value); +} + +String DateTimeLocalInputType::FormatDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) const { + if (!date_time_fields_state.HasDayOfMonth() || + !date_time_fields_state.HasMonth() || !date_time_fields_state.HasYear() || + !date_time_fields_state.HasHour() || + !date_time_fields_state.HasMinute() || !date_time_fields_state.HasAMPM()) + return g_empty_string; + + if (date_time_fields_state.HasMillisecond() && + date_time_fields_state.Millisecond()) { + return String::Format( + "%04u-%02u-%02uT%02u:%02u:%02u.%03u", date_time_fields_state.Year(), + date_time_fields_state.Month(), date_time_fields_state.DayOfMonth(), + date_time_fields_state.Hour23(), date_time_fields_state.Minute(), + date_time_fields_state.HasSecond() ? date_time_fields_state.Second() + : 0, + date_time_fields_state.Millisecond()); + } + + if (date_time_fields_state.HasSecond() && date_time_fields_state.Second()) { + return String::Format( + "%04u-%02u-%02uT%02u:%02u:%02u", date_time_fields_state.Year(), + date_time_fields_state.Month(), date_time_fields_state.DayOfMonth(), + date_time_fields_state.Hour23(), date_time_fields_state.Minute(), + date_time_fields_state.Second()); + } + + return String::Format( + "%04u-%02u-%02uT%02u:%02u", date_time_fields_state.Year(), + date_time_fields_state.Month(), date_time_fields_state.DayOfMonth(), + date_time_fields_state.Hour23(), date_time_fields_state.Minute()); +} + +void DateTimeLocalInputType::SetupLayoutParameters( + DateTimeEditElement::LayoutParameters& layout_parameters, + const DateComponents& date) const { + if (ShouldHaveSecondField(date)) { + layout_parameters.date_time_format = + layout_parameters.locale.DateTimeFormatWithSeconds(); + layout_parameters.fallback_date_time_format = "yyyy-MM-dd'T'HH:mm:ss"; + } else { + layout_parameters.date_time_format = + layout_parameters.locale.DateTimeFormatWithoutSeconds(); + layout_parameters.fallback_date_time_format = "yyyy-MM-dd'T'HH:mm"; + } + if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr), + &layout_parameters.minimum)) + layout_parameters.minimum = DateComponents(); + if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr), + &layout_parameters.maximum)) + layout_parameters.maximum = DateComponents(); + layout_parameters.placeholder_for_day = GetLocale().QueryString( + WebLocalizedString::kPlaceholderForDayOfMonthField); + layout_parameters.placeholder_for_month = + GetLocale().QueryString(WebLocalizedString::kPlaceholderForMonthField); + layout_parameters.placeholder_for_year = + GetLocale().QueryString(WebLocalizedString::kPlaceholderForYearField); +} + +bool DateTimeLocalInputType::IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const { + return has_year && has_month && has_day && has_ampm && has_hour && has_minute; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h new file mode 100644 index 00000000000..102cdc3e239 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_local_input_type.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_LOCAL_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_LOCAL_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" + +namespace blink { + +class ExceptionState; + +class DateTimeLocalInputType final : public BaseTemporalInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + explicit DateTimeLocalInputType(HTMLInputElement& element) + : BaseTemporalInputType(element) {} + + void CountUsage() override; + const AtomicString& FormControlType() const override; + double ValueAsDate() const override; + void SetValueAsDate(double, ExceptionState&) const override; + StepRange CreateStepRange(AnyStepHandling) const override; + bool ParseToDateComponentsInternal(const String&, + DateComponents*) const override; + bool SetMillisecondToDateComponents(double, DateComponents*) const override; + String LocalizeValue(const String&) const override; + void WarnIfValueIsInvalid(const String&) const override; + + // BaseTemporalInputType functions + String FormatDateTimeFieldsState(const DateTimeFieldsState&) const final; + void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&, + const DateComponents&) const final; + bool IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_LOCAL_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc new file mode 100644 index 00000000000..33268c41aa8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.cc @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h" + +#include "third_party/blink/renderer/core/css_property_names.h" +#include "third_party/blink/renderer/core/css_value_keywords.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/platform/fonts/font.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/text/text_run.h" + +namespace blink { + +int DateTimeNumericFieldElement::Range::ClampValue(int value) const { + return std::min(std::max(value, minimum), maximum); +} + +bool DateTimeNumericFieldElement::Range::IsInRange(int value) const { + return value >= minimum && value <= maximum; +} + +// ---------------------------- + +DateTimeNumericFieldElement::DateTimeNumericFieldElement( + Document& document, + FieldOwner& field_owner, + const Range& range, + const Range& hard_limits, + const String& placeholder, + const DateTimeNumericFieldElement::Step& step) + : DateTimeFieldElement(document, field_owner), + placeholder_(placeholder), + range_(range), + hard_limits_(hard_limits), + step_(step), + value_(0), + has_value_(false) { + DCHECK_NE(step_.step, 0); + DCHECK_LE(range_.minimum, range_.maximum); + DCHECK_LE(hard_limits_.minimum, hard_limits_.maximum); + + // We show a direction-neutral string such as "--" as a placeholder. It + // should follow the direction of numeric values. + if (LocaleForOwner().IsRTL()) { + WTF::Unicode::CharDirection dir = + WTF::Unicode::Direction(FormatValue(Maximum())[0]); + if (dir == WTF::Unicode::kLeftToRight || + dir == WTF::Unicode::kEuropeanNumber || + dir == WTF::Unicode::kArabicNumber) { + SetInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValueBidiOverride); + SetInlineStyleProperty(CSSPropertyDirection, CSSValueLtr); + } + } +} + +float DateTimeNumericFieldElement::MaximumWidth(const ComputedStyle& style) { + float maximum_width = ComputeTextWidth(style, placeholder_); + maximum_width = + std::max(maximum_width, ComputeTextWidth(style, FormatValue(Maximum()))); + maximum_width = std::max(maximum_width, ComputeTextWidth(style, Value())); + return maximum_width + DateTimeFieldElement::MaximumWidth(style); +} + +int DateTimeNumericFieldElement::DefaultValueForStepDown() const { + return range_.maximum; +} + +int DateTimeNumericFieldElement::DefaultValueForStepUp() const { + return range_.minimum; +} + +void DateTimeNumericFieldElement::SetFocused(bool value, + WebFocusType focus_type) { + if (!value) { + int value = TypeAheadValue(); + type_ahead_buffer_.Clear(); + if (value >= 0) + SetValueAsInteger(value, kDispatchEvent); + } + DateTimeFieldElement::SetFocused(value, focus_type); +} + +String DateTimeNumericFieldElement::FormatValue(int value) const { + Locale& locale = LocaleForOwner(); + if (hard_limits_.maximum > 999) + return locale.ConvertToLocalizedNumber(String::Format("%04d", value)); + if (hard_limits_.maximum > 99) + return locale.ConvertToLocalizedNumber(String::Format("%03d", value)); + return locale.ConvertToLocalizedNumber(String::Format("%02d", value)); +} + +void DateTimeNumericFieldElement::HandleKeyboardEvent( + KeyboardEvent* keyboard_event) { + DCHECK(!IsDisabled()); + if (keyboard_event->type() != EventTypeNames::keypress) + return; + + UChar char_code = static_cast<UChar>(keyboard_event->charCode()); + String number = + LocaleForOwner().ConvertFromLocalizedNumber(String(&char_code, 1)); + const int digit = number[0] - '0'; + if (digit < 0 || digit > 9) + return; + + unsigned maximum_length = + DateTimeNumericFieldElement::FormatValue(range_.maximum).length(); + if (type_ahead_buffer_.length() >= maximum_length) { + String current = type_ahead_buffer_.ToString(); + type_ahead_buffer_.Clear(); + unsigned desired_length = maximum_length - 1; + type_ahead_buffer_.Append(current, current.length() - desired_length, + desired_length); + } + type_ahead_buffer_.Append(number); + int new_value = TypeAheadValue(); + if (new_value >= hard_limits_.minimum) { + SetValueAsInteger(new_value, kDispatchEvent); + } else { + has_value_ = false; + UpdateVisibleValue(kDispatchEvent); + } + + if (type_ahead_buffer_.length() >= maximum_length || + new_value * 10 > range_.maximum) + FocusOnNextField(); + + keyboard_event->SetDefaultHandled(); +} + +bool DateTimeNumericFieldElement::HasValue() const { + return has_value_; +} + +void DateTimeNumericFieldElement::Initialize(const AtomicString& pseudo, + const String& ax_help_text) { + DateTimeFieldElement::Initialize(pseudo, ax_help_text, range_.minimum, + range_.maximum); +} + +int DateTimeNumericFieldElement::Maximum() const { + return range_.maximum; +} + +void DateTimeNumericFieldElement::SetEmptyValue(EventBehavior event_behavior) { + if (IsDisabled()) + return; + + has_value_ = false; + value_ = 0; + type_ahead_buffer_.Clear(); + UpdateVisibleValue(event_behavior); +} + +void DateTimeNumericFieldElement::SetValueAsInteger( + int value, + EventBehavior event_behavior) { + value_ = hard_limits_.ClampValue(value); + has_value_ = true; + UpdateVisibleValue(event_behavior); +} + +void DateTimeNumericFieldElement::StepDown() { + int new_value = + RoundDown(has_value_ ? value_ - 1 : DefaultValueForStepDown()); + if (!range_.IsInRange(new_value)) + new_value = RoundDown(range_.maximum); + type_ahead_buffer_.Clear(); + SetValueAsInteger(new_value, kDispatchEvent); +} + +void DateTimeNumericFieldElement::StepUp() { + int new_value = RoundUp(has_value_ ? value_ + 1 : DefaultValueForStepUp()); + if (!range_.IsInRange(new_value)) + new_value = RoundUp(range_.minimum); + type_ahead_buffer_.Clear(); + SetValueAsInteger(new_value, kDispatchEvent); +} + +String DateTimeNumericFieldElement::Value() const { + return has_value_ ? FormatValue(value_) : g_empty_string; +} + +int DateTimeNumericFieldElement::ValueAsInteger() const { + return has_value_ ? value_ : -1; +} + +int DateTimeNumericFieldElement::TypeAheadValue() const { + if (type_ahead_buffer_.length()) + return type_ahead_buffer_.ToString().ToInt(); + return -1; +} + +String DateTimeNumericFieldElement::VisibleValue() const { + if (type_ahead_buffer_.length()) + return FormatValue(TypeAheadValue()); + return has_value_ ? Value() : placeholder_; +} + +int DateTimeNumericFieldElement::RoundDown(int n) const { + n -= step_.step_base; + if (n >= 0) + n = n / step_.step * step_.step; + else + n = -((-n + step_.step - 1) / step_.step * step_.step); + return n + step_.step_base; +} + +int DateTimeNumericFieldElement::RoundUp(int n) const { + n -= step_.step_base; + if (n >= 0) + n = (n + step_.step - 1) / step_.step * step_.step; + else + n = -(-n / step_.step * step_.step); + return n + step_.step_base; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h new file mode 100644 index 00000000000..eeb5fb5d978 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_numeric_field_element.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_NUMERIC_FIELD_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_NUMERIC_FIELD_ELEMENT_H_ + +#include "base/macros.h" +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/renderer/core/html/forms/date_time_field_element.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// DateTimeNumericFieldElement represents numeric field of date time format, +// such as: +// - hour +// - minute +// - millisecond +// - second +// - year +class DateTimeNumericFieldElement : public DateTimeFieldElement { + public: + struct Step { + DISALLOW_NEW(); + Step(int step = 1, int step_base = 0) : step(step), step_base(step_base) {} + int step; + int step_base; + }; + + struct Range { + DISALLOW_NEW(); + Range(int minimum, int maximum) : minimum(minimum), maximum(maximum) {} + int ClampValue(int) const; + bool IsInRange(int) const; + bool IsSingleton() const { return minimum == maximum; } + + int minimum; + int maximum; + }; + + protected: + DateTimeNumericFieldElement(Document&, + FieldOwner&, + const Range&, + const Range& hard_limits, + const String& placeholder, + const Step& = Step()); + + int ClampValue(int value) const { return range_.ClampValue(value); } + virtual int DefaultValueForStepDown() const; + virtual int DefaultValueForStepUp() const; + const Range& GetRange() const { return range_; } + + // DateTimeFieldElement functions. + bool HasValue() const final; + void Initialize(const AtomicString& pseudo, const String& ax_help_text); + int Maximum() const; + void SetEmptyValue(EventBehavior = kDispatchNoEvent) final; + void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) override; + int ValueAsInteger() const final; + String VisibleValue() const final; + + private: + // DateTimeFieldElement functions. + void HandleKeyboardEvent(KeyboardEvent*) final; + float MaximumWidth(const ComputedStyle&) override; + void StepDown() final; + void StepUp() final; + String Value() const final; + + // Node functions. + void SetFocused(bool, WebFocusType) final; + + String FormatValue(int) const; + int RoundUp(int) const; + int RoundDown(int) const; + int TypeAheadValue() const; + + const String placeholder_; + const Range range_; + const Range hard_limits_; + const Step step_; + int value_; + bool has_value_; + mutable StringBuilder type_ahead_buffer_; + + DISALLOW_COPY_AND_ASSIGN(DateTimeNumericFieldElement); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc new file mode 100644 index 00000000000..5039df9f369 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.cc @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h" + +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/platform/fonts/font.h" +#include "third_party/blink/renderer/platform/text/text_break_iterator.h" +#include "third_party/blink/renderer/platform/text/text_run.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/unicode.h" + +namespace blink { + +static AtomicString MakeVisibleEmptyValue(const Vector<String>& symbols) { + unsigned maximum_length = 0; + for (unsigned index = 0; index < symbols.size(); ++index) + maximum_length = + std::max(maximum_length, NumGraphemeClusters(symbols[index])); + StringBuilder builder; + builder.ReserveCapacity(maximum_length); + for (unsigned length = 0; length < maximum_length; ++length) + builder.Append('-'); + return builder.ToAtomicString(); +} + +DateTimeSymbolicFieldElement::DateTimeSymbolicFieldElement( + Document& document, + FieldOwner& field_owner, + const Vector<String>& symbols, + int minimum, + int maximum) + : DateTimeFieldElement(document, field_owner), + symbols_(symbols), + visible_empty_value_(MakeVisibleEmptyValue(symbols)), + selected_index_(-1), + type_ahead_(this), + minimum_index_(minimum), + maximum_index_(maximum) { + DCHECK(!symbols.IsEmpty()); + DCHECK_GE(minimum_index_, 0); + SECURITY_DCHECK(maximum_index_ < static_cast<int>(symbols_.size())); + DCHECK_LE(minimum_index_, maximum_index_); +} + +float DateTimeSymbolicFieldElement::MaximumWidth(const ComputedStyle& style) { + float maximum_width = ComputeTextWidth(style, VisibleEmptyValue()); + for (unsigned index = 0; index < symbols_.size(); ++index) + maximum_width = + std::max(maximum_width, ComputeTextWidth(style, symbols_[index])); + return maximum_width + DateTimeFieldElement::MaximumWidth(style); +} + +void DateTimeSymbolicFieldElement::HandleKeyboardEvent( + KeyboardEvent* keyboard_event) { + if (keyboard_event->type() != EventTypeNames::keypress) + return; + + const UChar char_code = WTF::Unicode::ToLower(keyboard_event->charCode()); + if (char_code < ' ') + return; + + keyboard_event->SetDefaultHandled(); + + int index = type_ahead_.HandleEvent( + keyboard_event, TypeAhead::kMatchPrefix | TypeAhead::kCycleFirstChar | + TypeAhead::kMatchIndex); + if (index < 0) + return; + SetValueAsInteger(index, kDispatchEvent); +} + +bool DateTimeSymbolicFieldElement::HasValue() const { + return selected_index_ >= 0; +} + +void DateTimeSymbolicFieldElement::Initialize(const AtomicString& pseudo, + const String& ax_help_text) { + // The minimum and maximum below are exposed to users, and 1-based numbers + // are natural for symbolic fields. For example, the minimum value of a + // month field should be 1, not 0. + DateTimeFieldElement::Initialize(pseudo, ax_help_text, minimum_index_ + 1, + maximum_index_ + 1); +} + +void DateTimeSymbolicFieldElement::SetEmptyValue(EventBehavior event_behavior) { + if (IsDisabled()) + return; + selected_index_ = kInvalidIndex; + UpdateVisibleValue(event_behavior); +} + +void DateTimeSymbolicFieldElement::SetValueAsInteger( + int new_selected_index, + EventBehavior event_behavior) { + selected_index_ = std::max( + 0, std::min(new_selected_index, static_cast<int>(symbols_.size() - 1))); + UpdateVisibleValue(event_behavior); +} + +void DateTimeSymbolicFieldElement::StepDown() { + if (HasValue()) { + if (!IndexIsInRange(--selected_index_)) + selected_index_ = maximum_index_; + } else { + selected_index_ = maximum_index_; + } + UpdateVisibleValue(kDispatchEvent); +} + +void DateTimeSymbolicFieldElement::StepUp() { + if (HasValue()) { + if (!IndexIsInRange(++selected_index_)) + selected_index_ = minimum_index_; + } else { + selected_index_ = minimum_index_; + } + UpdateVisibleValue(kDispatchEvent); +} + +String DateTimeSymbolicFieldElement::Value() const { + return HasValue() ? symbols_[selected_index_] : g_empty_string; +} + +int DateTimeSymbolicFieldElement::ValueAsInteger() const { + return selected_index_; +} + +int DateTimeSymbolicFieldElement::ValueForARIAValueNow() const { + // Synchronize with minimum/maximum adjustment in initialize(). + return selected_index_ + 1; +} + +String DateTimeSymbolicFieldElement::VisibleEmptyValue() const { + return visible_empty_value_; +} + +String DateTimeSymbolicFieldElement::VisibleValue() const { + return HasValue() ? symbols_[selected_index_] : VisibleEmptyValue(); +} + +int DateTimeSymbolicFieldElement::IndexOfSelectedOption() const { + return selected_index_; +} + +int DateTimeSymbolicFieldElement::OptionCount() const { + return symbols_.size(); +} + +String DateTimeSymbolicFieldElement::OptionAtIndex(int index) const { + return symbols_[index]; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h new file mode 100644 index 00000000000..8f515406d1e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/date_time_symbolic_field_element.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_SYMBOLIC_FIELD_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_DATE_TIME_SYMBOLIC_FIELD_ELEMENT_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/html/forms/date_time_field_element.h" +#include "third_party/blink/renderer/core/html/forms/type_ahead.h" + +namespace blink { + +// DateTimeSymbolicFieldElement represents non-numeric field of data time +// format, such as: AM/PM, and month. +class DateTimeSymbolicFieldElement : public DateTimeFieldElement, + public TypeAheadDataSource { + protected: + DateTimeSymbolicFieldElement(Document&, + FieldOwner&, + const Vector<String>&, + int minimum, + int maximum); + size_t SymbolsSize() const { return symbols_.size(); } + bool HasValue() const final; + void Initialize(const AtomicString& pseudo, const String& ax_help_text); + void SetEmptyValue(EventBehavior = kDispatchNoEvent) final; + void SetValueAsInteger(int, EventBehavior = kDispatchNoEvent) final; + int ValueAsInteger() const final; + + private: + static const int kInvalidIndex = -1; + + String VisibleEmptyValue() const; + bool IndexIsInRange(int index) const { + return index >= minimum_index_ && index <= maximum_index_; + } + + // DateTimeFieldElement functions. + void HandleKeyboardEvent(KeyboardEvent*) final; + float MaximumWidth(const ComputedStyle&) override; + void StepDown() final; + void StepUp() final; + String Value() const final; + int ValueForARIAValueNow() const final; + String VisibleValue() const final; + + // TypeAheadDataSource functions. + int IndexOfSelectedOption() const override; + int OptionCount() const override; + String OptionAtIndex(int index) const override; + + const Vector<String> symbols_; + + // We use AtomicString to share visible empty value among multiple + // DateTimeEditElements in the page. + const AtomicString visible_empty_value_; + int selected_index_; + TypeAhead type_ahead_; + const int minimum_index_; + const int maximum_index_; + + DISALLOW_COPY_AND_ASSIGN(DateTimeSymbolicFieldElement); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc new file mode 100644 index 00000000000..4955ba9d605 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.cc @@ -0,0 +1,322 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com> + * Copyright (C) 2010 Google Inc. 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 "third_party/blink/renderer/core/html/forms/email_input_type.h" + +#include <unicode/idna.h> +#include <unicode/unistr.h> +#include <unicode/uvernum.h> +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +#if U_ICU_VERSION_MAJOR_NUM >= 59 +#include <unicode/char16ptr.h> +#endif + +namespace blink { + +using blink::WebLocalizedString; + +// http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address +static const char kLocalPartCharacters[] = + "abcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+/=?^_`{|}~.-"; +static const char kEmailPattern[] = + "[a-z0-9!#$%&'*+/=?^_`{|}~.-]+" // local part + "@" + "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?" // domain part + "(?:\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*"; + +// RFC5321 says the maximum total length of a domain name is 255 octets. +static const int32_t kMaximumDomainNameLength = 255; +// Use the same option as in url/url_canon_icu.cc +static const int32_t kIdnaConversionOption = UIDNA_CHECK_BIDI; + +std::unique_ptr<ScriptRegexp> EmailInputType::CreateEmailRegexp() { + return std::make_unique<ScriptRegexp>(kEmailPattern, + kTextCaseUnicodeInsensitive); +} + +String EmailInputType::ConvertEmailAddressToASCII(const ScriptRegexp& regexp, + const String& address) { + if (address.ContainsOnlyASCII()) + return address; + + size_t at_position = address.find('@'); + if (at_position == kNotFound) + return address; + String host = address.Substring(at_position + 1); + + // UnicodeString ctor for copy-on-write does not work reliably (in debug + // build.) TODO(jshin): In an unlikely case this is a perf-issue, treat + // 8bit and non-8bit strings separately. + host.Ensure16Bit(); + icu::UnicodeString idn_domain_name(host.Characters16(), host.length()); + icu::UnicodeString domain_name; + + // Leak |idna| at the end. + UErrorCode error_code = U_ZERO_ERROR; + static icu::IDNA* idna = + icu::IDNA::createUTS46Instance(kIdnaConversionOption, error_code); + DCHECK(idna); + icu::IDNAInfo idna_info; + idna->nameToASCII(idn_domain_name, domain_name, idna_info, error_code); + if (U_FAILURE(error_code) || idna_info.hasErrors() || + domain_name.length() > kMaximumDomainNameLength) + return address; + + StringBuilder builder; + builder.Append(address, 0, at_position + 1); +#if U_ICU_VERSION_MAJOR_NUM >= 59 + builder.Append(icu::toUCharPtr(domain_name.getBuffer()), domain_name.length()); +#else + builder.Append(domain_name.getBuffer(), domain_name.length()); +#endif + String ascii_email = builder.ToString(); + return IsValidEmailAddress(regexp, ascii_email) ? ascii_email : address; +} + +String EmailInputType::ConvertEmailAddressToUnicode( + const String& address) const { + if (!address.ContainsOnlyASCII()) + return address; + + size_t at_position = address.find('@'); + if (at_position == kNotFound) + return address; + + if (address.Find("xn--", at_position + 1) == kNotFound) + return address; + + String unicode_host = Platform::Current()->ConvertIDNToUnicode( + address.Substring(at_position + 1)); + StringBuilder builder; + builder.Append(address, 0, at_position + 1); + builder.Append(unicode_host); + return builder.ToString(); +} + +static bool IsInvalidLocalPartCharacter(UChar ch) { + if (!IsASCII(ch)) + return true; + DEFINE_STATIC_LOCAL(const String, valid_characters, (kLocalPartCharacters)); + return valid_characters.find(ToASCIILower(ch)) == kNotFound; +} + +static bool IsInvalidDomainCharacter(UChar ch) { + if (!IsASCII(ch)) + return true; + return !IsASCIILower(ch) && !IsASCIIUpper(ch) && !IsASCIIDigit(ch) && + ch != '.' && ch != '-'; +} + +static bool CheckValidDotUsage(const String& domain) { + if (domain.IsEmpty()) + return true; + if (domain[0] == '.' || domain[domain.length() - 1] == '.') + return false; + return domain.Find("..") == kNotFound; +} + +bool EmailInputType::IsValidEmailAddress(const ScriptRegexp& regexp, + const String& address) { + int address_length = address.length(); + if (!address_length) + return false; + + int match_length; + int match_offset = regexp.Match(address, 0, &match_length); + + return !match_offset && match_length == address_length; +} + +EmailInputType::EmailInputType(HTMLInputElement& element) + : BaseTextInputType(element) {} + +InputType* EmailInputType::Create(HTMLInputElement& element) { + return new EmailInputType(element); +} + +void EmailInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeEmail); + bool has_max_length = GetElement().FastHasAttribute(HTMLNames::maxlengthAttr); + if (has_max_length) + CountUsageIfVisible(WebFeature::kInputTypeEmailMaxLength); + if (GetElement().Multiple()) { + CountUsageIfVisible(WebFeature::kInputTypeEmailMultiple); + if (has_max_length) + CountUsageIfVisible(WebFeature::kInputTypeEmailMultipleMaxLength); + } +} + +const AtomicString& EmailInputType::FormControlType() const { + return InputTypeNames::email; +} + +ScriptRegexp& EmailInputType::EnsureEmailRegexp() const { + if (!email_regexp_) + email_regexp_ = CreateEmailRegexp(); + return *email_regexp_; +} + +// The return value is an invalid email address string if the specified string +// contains an invalid email address. Otherwise, null string is returned. +// If an empty string is returned, it means empty address is specified. +// e.g. "foo@example.com,,bar@example.com" for multiple case. +String EmailInputType::FindInvalidAddress(const String& value) const { + if (value.IsEmpty()) + return String(); + if (!GetElement().Multiple()) + return IsValidEmailAddress(EnsureEmailRegexp(), value) ? String() : value; + Vector<String> addresses; + value.Split(',', true, addresses); + for (const auto& address : addresses) { + String stripped = StripLeadingAndTrailingHTMLSpaces(address); + if (!IsValidEmailAddress(EnsureEmailRegexp(), stripped)) + return stripped; + } + return String(); +} + +bool EmailInputType::TypeMismatchFor(const String& value) const { + return !FindInvalidAddress(value).IsNull(); +} + +bool EmailInputType::TypeMismatch() const { + return TypeMismatchFor(GetElement().value()); +} + +String EmailInputType::TypeMismatchText() const { + String invalid_address = FindInvalidAddress(GetElement().value()); + DCHECK(!invalid_address.IsNull()); + if (invalid_address.IsEmpty()) + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailEmpty); + String at_sign = String("@"); + size_t at_index = invalid_address.find('@'); + if (at_index == kNotFound) + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailNoAtSign, at_sign, + invalid_address); + // We check validity against an ASCII value because of difficulty to check + // invalid characters. However we should show Unicode value. + String unicode_address = ConvertEmailAddressToUnicode(invalid_address); + String local_part = invalid_address.Left(at_index); + String domain = invalid_address.Substring(at_index + 1); + if (local_part.IsEmpty()) + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailEmptyLocal, at_sign, + unicode_address); + if (domain.IsEmpty()) + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailEmptyDomain, at_sign, + unicode_address); + size_t invalid_char_index = local_part.Find(IsInvalidLocalPartCharacter); + if (invalid_char_index != kNotFound) { + unsigned char_length = U_IS_LEAD(local_part[invalid_char_index]) ? 2 : 1; + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailInvalidLocal, + at_sign, local_part.Substring(invalid_char_index, char_length)); + } + invalid_char_index = domain.Find(IsInvalidDomainCharacter); + if (invalid_char_index != kNotFound) { + unsigned char_length = U_IS_LEAD(domain[invalid_char_index]) ? 2 : 1; + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailInvalidDomain, + at_sign, domain.Substring(invalid_char_index, char_length)); + } + if (!CheckValidDotUsage(domain)) { + size_t at_index_in_unicode = unicode_address.find('@'); + DCHECK_NE(at_index_in_unicode, kNotFound); + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmailInvalidDots, + String("."), unicode_address.Substring(at_index_in_unicode + 1)); + } + if (GetElement().Multiple()) + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForMultipleEmail); + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForEmail); +} + +bool EmailInputType::SupportsSelectionAPI() const { + return false; +} + +String EmailInputType::SanitizeValue(const String& proposed_value) const { + String no_line_break_value = proposed_value.RemoveCharacters(IsHTMLLineBreak); + if (!GetElement().Multiple()) + return StripLeadingAndTrailingHTMLSpaces(no_line_break_value); + Vector<String> addresses; + no_line_break_value.Split(',', true, addresses); + StringBuilder stripped_value; + for (size_t i = 0; i < addresses.size(); ++i) { + if (i > 0) + stripped_value.Append(','); + stripped_value.Append(StripLeadingAndTrailingHTMLSpaces(addresses[i])); + } + return stripped_value.ToString(); +} + +String EmailInputType::ConvertFromVisibleValue( + const String& visible_value) const { + String sanitized_value = SanitizeValue(visible_value); + if (!GetElement().Multiple()) + return ConvertEmailAddressToASCII(EnsureEmailRegexp(), sanitized_value); + Vector<String> addresses; + sanitized_value.Split(',', true, addresses); + StringBuilder builder; + builder.ReserveCapacity(sanitized_value.length()); + for (size_t i = 0; i < addresses.size(); ++i) { + if (i > 0) + builder.Append(','); + builder.Append( + ConvertEmailAddressToASCII(EnsureEmailRegexp(), addresses[i])); + } + return builder.ToString(); +} + +String EmailInputType::VisibleValue() const { + String value = GetElement().value(); + if (!GetElement().Multiple()) + return ConvertEmailAddressToUnicode(value); + + Vector<String> addresses; + value.Split(',', true, addresses); + StringBuilder builder; + builder.ReserveCapacity(value.length()); + for (size_t i = 0; i < addresses.size(); ++i) { + if (i > 0) + builder.Append(','); + builder.Append(ConvertEmailAddressToUnicode(addresses[i])); + } + return builder.ToString(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/email_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.h new file mode 100644 index 00000000000..58a6e745bb0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/email_input_type.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EMAIL_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EMAIL_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h" + +namespace blink { + +class EmailInputType final : public BaseTextInputType { + public: + static InputType* Create(HTMLInputElement&); + + // They are public for unit testing. + CORE_EXPORT static String ConvertEmailAddressToASCII(const ScriptRegexp&, + const String&); + CORE_EXPORT static bool IsValidEmailAddress(const ScriptRegexp&, + const String&); + CORE_EXPORT static std::unique_ptr<ScriptRegexp> CreateEmailRegexp(); + + private: + explicit EmailInputType(HTMLInputElement&); + void CountUsage() override; + const AtomicString& FormControlType() const override; + bool TypeMismatchFor(const String&) const override; + bool TypeMismatch() const override; + String TypeMismatchText() const override; + bool SupportsSelectionAPI() const override; + String SanitizeValue(const String&) const override; + String ConvertFromVisibleValue(const String&) const override; + String VisibleValue() const override; + + ScriptRegexp& EnsureEmailRegexp() const; + String ConvertEmailAddressToUnicode(const String&) const; + String FindInvalidAddress(const String&) const; + + mutable std::unique_ptr<ScriptRegexp> email_regexp_; +}; + +} // namespace blink + +#endif // ButtonInputType_h diff --git a/chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc b/chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc new file mode 100644 index 00000000000..b702e425cc4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/email_input_type_test.cc @@ -0,0 +1,79 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/email_input_type.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h" + +namespace blink { + +namespace { + +void ExpectToSucceed(const String& source) { + std::unique_ptr<ScriptRegexp> email_regexp = + EmailInputType::CreateEmailRegexp(); + String result = + EmailInputType::ConvertEmailAddressToASCII(*email_regexp, source); + EXPECT_NE(source, result); + EXPECT_TRUE(EmailInputType::IsValidEmailAddress(*email_regexp, result)); +} + +void ExpectToFail(const String& source) { + std::unique_ptr<ScriptRegexp> email_regexp = + EmailInputType::CreateEmailRegexp(); + // Conversion failed. The resultant value might contains non-ASCII + // characters, and not a valid email address. + EXPECT_FALSE(EmailInputType::IsValidEmailAddress( + *email_regexp, + EmailInputType::ConvertEmailAddressToASCII(*email_regexp, source))); +} + +} // namespace + +TEST(EmailInputTypeTest, ConvertEmailAddressToASCII) { + // U+043C U+043E U+0439 . U+0434 U+043E U+043C U+0435 U+043D + ExpectToFail( + String::FromUTF8("user@\xD0\xBC\xD0\xBE\xD0\xB9." + "\xD0\xB4\xD0\xBE\xD0\xBC\xD0\xB5\xD0\xBD@")); + ExpectToFail( + String::FromUTF8("user@\xD0\xBC\xD0\xBE\xD0\xB9. " + "\xD0\xB4\xD0\xBE\xD0\xBC\xD0\xB5\xD0\xBD")); + ExpectToFail( + String::FromUTF8("user@\xD0\xBC\xD0\xBE\xD0\xB9." + "\t\xD0\xB4\xD0\xBE\xD0\xBC\xD0\xB5\xD0\xBD")); +} + +TEST(EmailInputTypeTest, ConvertEmailAddressToASCIIUTS46) { + // http://unicode.org/reports/tr46/#Table_IDNA_Comparisons + + // U+00E0 + ExpectToSucceed(String::FromUTF8("foo@\xC3\xA0.com")); + // U+FF01 + ExpectToFail(String::FromUTF8("foo@\xEF\xBC\x81.com")); + + // U+2132 + ExpectToFail(String::FromUTF8("foo@\xE2\x84\xB2.com")); + // U+2F868 + ExpectToFail(String::FromUTF8("foo@\xF0\xAF\xA1\xA8.com")); + + // U+00C0 + ExpectToSucceed(String::FromUTF8("foo@\xC3\x80.com")); + // U+2665 + ExpectToSucceed(String::FromUTF8("foo@\xE2\x99\xA5.com")); + // U+00DF + ExpectToSucceed(String::FromUTF8("foo@\xC3\x9F.com")); + + // U+0221 + ExpectToSucceed(String::FromUTF8("foo@\xC8\xA1.com")); + // U+0662 + ExpectToFail(String::FromUTF8("foo@\xD8\x82.com")); + + // U+2615 + ExpectToSucceed(String::FromUTF8("foo@\xE2\x98\x95.com")); + // U+023A + ExpectToSucceed(String::FromUTF8("foo@\xC8\xBA.com")); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc new file mode 100644 index 00000000000..a50c2db2d8c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/external_date_time_chooser.h" + +#include "third_party/blink/public/web/web_date_time_chooser_completion.h" +#include "third_party/blink/public/web/web_date_time_chooser_params.h" +#include "third_party/blink/public/web/web_view_client.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +class WebDateTimeChooserCompletionImpl : public WebDateTimeChooserCompletion { + public: + WebDateTimeChooserCompletionImpl(ExternalDateTimeChooser* chooser) + : chooser_(chooser) {} + + private: + void DidChooseValue(double value) override { + chooser_->DidChooseValue(value); + delete this; + } + + void DidCancelChooser() override { + chooser_->DidCancelChooser(); + delete this; + } + + Persistent<ExternalDateTimeChooser> chooser_; +}; + +ExternalDateTimeChooser::~ExternalDateTimeChooser() = default; + +void ExternalDateTimeChooser::Trace(blink::Visitor* visitor) { + visitor->Trace(client_); + DateTimeChooser::Trace(visitor); +} + +ExternalDateTimeChooser::ExternalDateTimeChooser(DateTimeChooserClient* client) + : client_(client) { + DCHECK(!RuntimeEnabledFeatures::InputMultipleFieldsUIEnabled()); + DCHECK(client); +} + +ExternalDateTimeChooser* ExternalDateTimeChooser::Create( + ChromeClient* chrome_client, + WebViewClient* web_view_client, + DateTimeChooserClient* client, + const DateTimeChooserParameters& parameters) { + DCHECK(chrome_client); + ExternalDateTimeChooser* chooser = new ExternalDateTimeChooser(client); + if (!chooser->OpenDateTimeChooser(chrome_client, web_view_client, parameters)) + chooser = nullptr; + return chooser; +} + +static WebDateTimeInputType ToWebDateTimeInputType(const AtomicString& source) { + if (source == InputTypeNames::date) + return kWebDateTimeInputTypeDate; + if (source == InputTypeNames::datetime) + return kWebDateTimeInputTypeDateTime; + if (source == InputTypeNames::datetime_local) + return kWebDateTimeInputTypeDateTimeLocal; + if (source == InputTypeNames::month) + return kWebDateTimeInputTypeMonth; + if (source == InputTypeNames::time) + return kWebDateTimeInputTypeTime; + if (source == InputTypeNames::week) + return kWebDateTimeInputTypeWeek; + return kWebDateTimeInputTypeNone; +} + +bool ExternalDateTimeChooser::OpenDateTimeChooser( + ChromeClient* chrome_client, + WebViewClient* web_view_client, + const DateTimeChooserParameters& parameters) { + if (!web_view_client) + return false; + + WebDateTimeChooserParams web_params; + web_params.type = ToWebDateTimeInputType(parameters.type); + web_params.anchor_rect_in_screen = parameters.anchor_rect_in_screen; + web_params.double_value = parameters.double_value; + web_params.suggestions = parameters.suggestions; + web_params.minimum = parameters.minimum; + web_params.maximum = parameters.maximum; + web_params.step = parameters.step; + web_params.step_base = parameters.step_base; + web_params.is_required = parameters.required; + web_params.is_anchor_element_rtl = parameters.is_anchor_element_rtl; + + WebDateTimeChooserCompletion* completion = + new WebDateTimeChooserCompletionImpl(this); + if (web_view_client->OpenDateTimeChooser(web_params, completion)) + return true; + // We can't open a chooser. Calling + // WebDateTimeChooserCompletionImpl::didCancelChooser to delete the + // WebDateTimeChooserCompletionImpl object and deref this. + completion->DidCancelChooser(); + return false; +} + +void ExternalDateTimeChooser::DidChooseValue(const WebString& value) { + if (client_) + client_->DidChooseValue(value); + // didChooseValue might run JavaScript code, and endChooser() might be + // called. However DateTimeChooserCompletionImpl still has one reference to + // this object. + if (client_) + client_->DidEndChooser(); +} + +void ExternalDateTimeChooser::DidChooseValue(double value) { + if (client_) + client_->DidChooseValue(value); + // didChooseValue might run JavaScript code, and endChooser() might be + // called. However DateTimeChooserCompletionImpl still has one reference to + // this object. + if (client_) + client_->DidEndChooser(); +} + +void ExternalDateTimeChooser::DidCancelChooser() { + if (client_) + client_->DidEndChooser(); +} + +void ExternalDateTimeChooser::EndChooser() { + DateTimeChooserClient* client = client_; + client_ = nullptr; + client->DidEndChooser(); +} + +AXObject* ExternalDateTimeChooser::RootAXObject() { + return nullptr; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h new file mode 100644 index 00000000000..19d512520a1 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/external_date_time_chooser.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_DATE_TIME_CHOOSER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_DATE_TIME_CHOOSER_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" + +namespace blink { + +class ChromeClient; +class DateTimeChooserClient; +class WebString; +class WebViewClient; + +class CORE_EXPORT ExternalDateTimeChooser final : public DateTimeChooser { + public: + static ExternalDateTimeChooser* Create(ChromeClient*, + WebViewClient*, + DateTimeChooserClient*, + const DateTimeChooserParameters&); + ~ExternalDateTimeChooser() override; + void Trace(blink::Visitor*) override; + + // The following functions are for DateTimeChooserCompletion. + void DidChooseValue(const WebString&); + void DidChooseValue(double); + void DidCancelChooser(); + + private: + ExternalDateTimeChooser(DateTimeChooserClient*); + bool OpenDateTimeChooser(ChromeClient*, + WebViewClient*, + const DateTimeChooserParameters&); + + // DateTimeChooser function: + void EndChooser() override; + AXObject* RootAXObject() override; + + Member<DateTimeChooserClient> client_; +}; +} +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc new file mode 100644 index 00000000000..3e5f3525d29 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.cc @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/external_popup_menu.h" + +#include "build/build_config.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_coalesced_input_event.h" +#include "third_party/blink/public/platform/web_mouse_event.h" +#include "third_party/blink/public/platform/web_vector.h" +#include "third_party/blink/public/web/web_external_popup_menu.h" +#include "third_party/blink/public/web/web_frame_client.h" +#include "third_party/blink/public/web/web_menu_item_info.h" +#include "third_party/blink/public/web/web_popup_menu_info.h" +#include "third_party/blink/public/web/web_view.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/events/current_input_event.h" +#include "third_party/blink/renderer/core/exported/web_view_impl.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/geometry/float_quad.h" +#include "third_party/blink/renderer/platform/geometry/int_point.h" +#include "third_party/blink/renderer/platform/text/text_direction.h" +#if defined(OS_MACOSX) +#include "third_party/blink/renderer/core/page/chrome_client.h" +#endif + +namespace blink { + +ExternalPopupMenu::ExternalPopupMenu(LocalFrame& frame, + HTMLSelectElement& owner_element, + WebView& web_view) + : owner_element_(owner_element), + local_frame_(frame), + web_view_(web_view), + dispatch_event_timer_(frame.GetTaskRunner(TaskType::kUnspecedTimer), + this, + &ExternalPopupMenu::DispatchEvent), + web_external_popup_menu_(nullptr) {} + +ExternalPopupMenu::~ExternalPopupMenu() = default; + +void ExternalPopupMenu::Trace(blink::Visitor* visitor) { + visitor->Trace(owner_element_); + visitor->Trace(local_frame_); + PopupMenu::Trace(visitor); +} + +bool ExternalPopupMenu::ShowInternal() { + // Blink core reuses the PopupMenu of an element. For simplicity, we do + // recreate the actual external popup everytime. + if (web_external_popup_menu_) { + web_external_popup_menu_->Close(); + web_external_popup_menu_ = nullptr; + } + + WebPopupMenuInfo info; + GetPopupMenuInfo(info, *owner_element_); + if (info.items.empty()) + return false; + WebLocalFrameImpl* webframe = + WebLocalFrameImpl::FromFrame(local_frame_.Get()); + web_external_popup_menu_ = + webframe->Client()->CreateExternalPopupMenu(info, this); + if (web_external_popup_menu_) { + LayoutObject* layout_object = owner_element_->GetLayoutObject(); + if (!layout_object || !layout_object->IsBox()) + return false; + FloatQuad quad(ToLayoutBox(layout_object) + ->LocalToAbsoluteQuad(FloatQuad( + ToLayoutBox(layout_object)->BorderBoundingBox()))); + IntRect rect(quad.EnclosingBoundingBox()); + IntRect rect_in_viewport = local_frame_->View()->ContentsToViewport(rect); + web_external_popup_menu_->Show(rect_in_viewport); + return true; + } + + // The client might refuse to create a popup (when there is already one + // pending to be shown for example). + DidCancel(); + return false; +} + +void ExternalPopupMenu::Show() { + if (!ShowInternal()) + return; +#if defined(OS_MACOSX) + const WebInputEvent* current_event = CurrentInputEvent::Get(); + if (current_event && current_event->GetType() == WebInputEvent::kMouseDown) { + synthetic_event_ = std::make_unique<WebMouseEvent>(); + *synthetic_event_ = *static_cast<const WebMouseEvent*>(current_event); + synthetic_event_->SetType(WebInputEvent::kMouseUp); + dispatch_event_timer_.StartOneShot(TimeDelta(), FROM_HERE); + // FIXME: show() is asynchronous. If preparing a popup is slow and a + // user released the mouse button before showing the popup, mouseup and + // click events are correctly dispatched. Dispatching the synthetic + // mouseup event is redundant in this case. + } +#endif +} + +void ExternalPopupMenu::DispatchEvent(TimerBase*) { + web_view_.HandleInputEvent(blink::WebCoalescedInputEvent(*synthetic_event_)); + synthetic_event_.reset(); +} + +void ExternalPopupMenu::Hide() { + if (owner_element_) + owner_element_->PopupDidHide(); + if (!web_external_popup_menu_) + return; + web_external_popup_menu_->Close(); + web_external_popup_menu_ = nullptr; +} + +void ExternalPopupMenu::UpdateFromElement(UpdateReason reason) { + switch (reason) { + case kBySelectionChange: + case kByDOMChange: + if (needs_update_) + return; + needs_update_ = true; + owner_element_->GetDocument() + .GetTaskRunner(TaskType::kUserInteraction) + ->PostTask(FROM_HERE, WTF::Bind(&ExternalPopupMenu::Update, + WrapPersistent(this))); + break; + + case kByStyleChange: + // TODO(tkent): We should update the popup location/content in some + // cases. e.g. Updating ComputedStyle of the SELECT element affects + // popup position and OPTION style. + break; + } +} + +void ExternalPopupMenu::Update() { + if (!web_external_popup_menu_ || !owner_element_) + return; + owner_element_->GetDocument().UpdateStyleAndLayoutTree(); + // disconnectClient() might have been called. + if (!owner_element_) + return; + needs_update_ = false; + + if (ShowInternal()) + return; + // We failed to show a popup. Notify it to the owner. + Hide(); +} + +void ExternalPopupMenu::DisconnectClient() { + Hide(); + owner_element_ = nullptr; +} + +void ExternalPopupMenu::DidChangeSelection(int index) {} + +void ExternalPopupMenu::DidAcceptIndex(int index) { + // Calling methods on the HTMLSelectElement might lead to this object being + // derefed. This ensures it does not get deleted while we are running this + // method. + int popup_menu_item_index = ToPopupMenuItemIndex(index, *owner_element_); + + if (owner_element_) { + owner_element_->PopupDidHide(); + owner_element_->SelectOptionByPopup(popup_menu_item_index); + } + web_external_popup_menu_ = nullptr; +} + +// Android uses this function even for single SELECT. +void ExternalPopupMenu::DidAcceptIndices(const WebVector<int>& indices) { + if (!owner_element_) { + web_external_popup_menu_ = nullptr; + return; + } + + HTMLSelectElement* owner_element = owner_element_; + owner_element->PopupDidHide(); + + if (indices.size() == 0) { + owner_element->SelectOptionByPopup(-1); + } else if (!owner_element->IsMultiple()) { + owner_element->SelectOptionByPopup( + ToPopupMenuItemIndex(indices[indices.size() - 1], *owner_element)); + } else { + Vector<int> list_indices; + list_indices.ReserveCapacity(indices.size()); + for (size_t i = 0; i < indices.size(); ++i) + list_indices.push_back(ToPopupMenuItemIndex(indices[i], *owner_element)); + owner_element->SelectMultipleOptionsByPopup(list_indices); + } + + web_external_popup_menu_ = nullptr; +} + +void ExternalPopupMenu::DidCancel() { + if (owner_element_) + owner_element_->PopupDidHide(); + web_external_popup_menu_ = nullptr; +} + +void ExternalPopupMenu::GetPopupMenuInfo(WebPopupMenuInfo& info, + HTMLSelectElement& owner_element) { + const HeapVector<Member<HTMLElement>>& list_items = + owner_element.GetListItems(); + size_t item_count = list_items.size(); + size_t count = 0; + Vector<WebMenuItemInfo> items(item_count); + for (size_t i = 0; i < item_count; ++i) { + if (owner_element.ItemIsDisplayNone(*list_items[i])) + continue; + + Element& item_element = *list_items[i]; + WebMenuItemInfo& popup_item = items[count++]; + popup_item.label = owner_element.ItemText(item_element); + popup_item.tool_tip = item_element.title(); + popup_item.checked = false; + if (IsHTMLHRElement(item_element)) { + popup_item.type = WebMenuItemInfo::kSeparator; + } else if (IsHTMLOptGroupElement(item_element)) { + popup_item.type = WebMenuItemInfo::kGroup; + } else { + popup_item.type = WebMenuItemInfo::kOption; + popup_item.checked = ToHTMLOptionElement(item_element).Selected(); + } + popup_item.enabled = !item_element.IsDisabledFormControl(); + const ComputedStyle& style = *owner_element.ItemComputedStyle(item_element); + popup_item.text_direction = ToWebTextDirection(style.Direction()); + popup_item.has_text_direction_override = IsOverride(style.GetUnicodeBidi()); + } + + const ComputedStyle& menu_style = owner_element.GetComputedStyle() + ? *owner_element.GetComputedStyle() + : *owner_element.EnsureComputedStyle(); + const SimpleFontData* font_data = menu_style.GetFont().PrimaryFont(); + DCHECK(font_data); + info.item_height = font_data ? font_data->GetFontMetrics().Height() : 0; + info.item_font_size = static_cast<int>( + menu_style.GetFont().GetFontDescription().ComputedSize()); + info.selected_index = ToExternalPopupMenuItemIndex( + owner_element.SelectedListIndex(), owner_element); + info.right_aligned = menu_style.Direction() == TextDirection::kRtl; + info.allow_multiple_selection = owner_element.IsMultiple(); + if (count < item_count) + items.Shrink(count); + info.items = items; +} + +int ExternalPopupMenu::ToPopupMenuItemIndex(int external_popup_menu_item_index, + HTMLSelectElement& owner_element) { + if (external_popup_menu_item_index < 0) + return external_popup_menu_item_index; + + int index_tracker = 0; + const HeapVector<Member<HTMLElement>>& items = owner_element.GetListItems(); + for (int i = 0; i < static_cast<int>(items.size()); ++i) { + if (owner_element.ItemIsDisplayNone(*items[i])) + continue; + if (index_tracker++ == external_popup_menu_item_index) + return i; + } + return -1; +} + +int ExternalPopupMenu::ToExternalPopupMenuItemIndex( + int popup_menu_item_index, + HTMLSelectElement& owner_element) { + if (popup_menu_item_index < 0) + return popup_menu_item_index; + + size_t index_tracker = 0; + const HeapVector<Member<HTMLElement>>& items = owner_element.GetListItems(); + for (int i = 0; i < static_cast<int>(items.size()); ++i) { + if (owner_element.ItemIsDisplayNone(*items[i])) + continue; + if (popup_menu_item_index == i) + return index_tracker; + ++index_tracker; + } + return -1; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h new file mode 100644 index 00000000000..742f8ddd661 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_POPUP_MENU_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_POPUP_MENU_H_ + +#include <memory> +#include "third_party/blink/public/platform/web_canvas.h" +#include "third_party/blink/public/platform/web_scrollbar.h" +#include "third_party/blink/public/web/web_external_popup_menu_client.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/popup_menu.h" +#include "third_party/blink/renderer/platform/timer.h" +#include "third_party/blink/renderer/platform/wtf/compiler.h" + +namespace blink { + +class HTMLSelectElement; +class LocalFrame; +class WebExternalPopupMenu; +class WebMouseEvent; +class WebView; +struct WebPopupMenuInfo; + +// The ExternalPopupMenu is a PopupMenu implementation for macOS and Android. +// It uses a OS-native menu implementation. +class CORE_EXPORT ExternalPopupMenu final : public PopupMenu, + public WebExternalPopupMenuClient { + public: + ExternalPopupMenu(LocalFrame&, HTMLSelectElement&, WebView&); + ~ExternalPopupMenu() override; + + // Fills |info| with the popup menu information contained in the + // PopupMenuClient associated with this ExternalPopupMenu. + // FIXME: public only for test access. Need to revert once gtest + // helpers from chromium are available for blink. + static void GetPopupMenuInfo(WebPopupMenuInfo&, HTMLSelectElement&); + static int ToPopupMenuItemIndex(int index, HTMLSelectElement&); + static int ToExternalPopupMenuItemIndex(int index, HTMLSelectElement&); + + void Trace(blink::Visitor*) override; + + private: + // PopupMenu methods: + void Show() override; + void Hide() override; + void UpdateFromElement(UpdateReason) override; + void DisconnectClient() override; + + // WebExternalPopupClient methods: + void DidChangeSelection(int index) override; + void DidAcceptIndex(int index) override; + void DidAcceptIndices(const WebVector<int>& indices) override; + void DidCancel() override; + + bool ShowInternal(); + void DispatchEvent(TimerBase*); + void Update(); + + Member<HTMLSelectElement> owner_element_; + Member<LocalFrame> local_frame_; + WebView& web_view_; + std::unique_ptr<WebMouseEvent> synthetic_event_; + TaskRunnerTimer<ExternalPopupMenu> dispatch_event_timer_; + // The actual implementor of the show menu. + WebExternalPopupMenu* web_external_popup_menu_; + bool needs_update_ = false; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_EXTERNAL_POPUP_MENU_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc new file mode 100644 index 00000000000..d9084519e22 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc @@ -0,0 +1,227 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/external_popup_menu.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_url_loader_mock_factory.h" +#include "third_party/blink/public/web/web_external_popup_menu.h" +#include "third_party/blink/public/web/web_popup_menu_info.h" +#include "third_party/blink/public/web/web_settings.h" +#include "third_party/blink/renderer/core/frame/frame_test_helpers.h" +#include "third_party/blink/renderer/core/frame/visual_viewport.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/html/forms/popup_menu.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_menu_list.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/url_test_helpers.h" + +namespace blink { + +class ExternalPopupMenuDisplayNoneItemsTest : public PageTestBase { + public: + ExternalPopupMenuDisplayNoneItemsTest() = default; + + protected: + void SetUp() override { + PageTestBase::SetUp(); + HTMLSelectElement* element = HTMLSelectElement::Create(GetDocument()); + // Set the 4th an 5th items to have "display: none" property + element->SetInnerHTMLFromString( + "<option><option><option><option style='display:none;'><option " + "style='display:none;'><option><option>"); + GetDocument().body()->AppendChild(element, ASSERT_NO_EXCEPTION); + owner_element_ = element; + GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + } + + Persistent<HTMLSelectElement> owner_element_; +}; + +TEST_F(ExternalPopupMenuDisplayNoneItemsTest, PopupMenuInfoSizeTest) { + WebPopupMenuInfo info; + ExternalPopupMenu::GetPopupMenuInfo(info, *owner_element_); + EXPECT_EQ(5U, info.items.size()); +} + +TEST_F(ExternalPopupMenuDisplayNoneItemsTest, IndexMappingTest) { + // 6th indexed item in popupmenu would be the 4th item in ExternalPopupMenu, + // and vice-versa. + EXPECT_EQ( + 4, ExternalPopupMenu::ToExternalPopupMenuItemIndex(6, *owner_element_)); + EXPECT_EQ(6, ExternalPopupMenu::ToPopupMenuItemIndex(4, *owner_element_)); + + // Invalid index, methods should return -1. + EXPECT_EQ( + -1, ExternalPopupMenu::ToExternalPopupMenuItemIndex(8, *owner_element_)); + EXPECT_EQ(-1, ExternalPopupMenu::ToPopupMenuItemIndex(8, *owner_element_)); +} + +class ExternalPopupMenuWebFrameClient + : public FrameTestHelpers::TestWebFrameClient { + public: + WebExternalPopupMenu* CreateExternalPopupMenu( + const WebPopupMenuInfo&, + WebExternalPopupMenuClient*) override { + return &mock_web_external_popup_menu_; + } + WebRect ShownBounds() const { + return mock_web_external_popup_menu_.ShownBounds(); + } + + private: + class MockWebExternalPopupMenu : public WebExternalPopupMenu { + void Show(const WebRect& bounds) override { shown_bounds_ = bounds; } + void Close() override {} + + public: + WebRect ShownBounds() const { return shown_bounds_; } + + private: + WebRect shown_bounds_; + }; + WebRect shown_bounds_; + MockWebExternalPopupMenu mock_web_external_popup_menu_; +}; + +class ExternalPopupMenuTest : public testing::Test { + public: + ExternalPopupMenuTest() : base_url_("http://www.test.com") {} + + protected: + void SetUp() override { + helper_.Initialize(&web_frame_client_); + WebView()->SetUseExternalPopupMenus(true); + } + void TearDown() override { + Platform::Current() + ->GetURLLoaderMockFactory() + ->UnregisterAllURLsAndClearMemoryCache(); + } + + void RegisterMockedURLLoad(const std::string& file_name) { + URLTestHelpers::RegisterMockedURLLoadFromBase( + WebString::FromUTF8(base_url_), test::CoreTestDataPath("popup"), + WebString::FromUTF8(file_name), WebString::FromUTF8("text/html")); + } + + void LoadFrame(const std::string& file_name) { + FrameTestHelpers::LoadFrame(MainFrame(), base_url_ + file_name); + WebView()->Resize(WebSize(800, 600)); + WebView()->UpdateAllLifecyclePhases(); + } + + WebViewImpl* WebView() const { return helper_.GetWebView(); } + const ExternalPopupMenuWebFrameClient& Client() const { + return web_frame_client_; + } + WebLocalFrameImpl* MainFrame() const { return helper_.LocalMainFrame(); } + + private: + std::string base_url_; + ExternalPopupMenuWebFrameClient web_frame_client_; + FrameTestHelpers::WebViewHelper helper_; +}; + +TEST_F(ExternalPopupMenuTest, PopupAccountsForVisualViewportTransform) { + RegisterMockedURLLoad("select_mid_screen.html"); + LoadFrame("select_mid_screen.html"); + + WebView()->Resize(WebSize(100, 100)); + WebView()->UpdateAllLifecyclePhases(); + + HTMLSelectElement* select = ToHTMLSelectElement( + MainFrame()->GetFrame()->GetDocument()->getElementById("select")); + LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); + ASSERT_TRUE(menu_list); + + VisualViewport& visual_viewport = WebView()->GetPage()->GetVisualViewport(); + + IntRect rect_in_document = menu_list->AbsoluteBoundingBoxRect(); + + constexpr int kScaleFactor = 2; + ScrollOffset scroll_delta(20, 30); + + const int expected_x = + (rect_in_document.X() - scroll_delta.Width()) * kScaleFactor; + const int expected_y = + (rect_in_document.Y() - scroll_delta.Height()) * kScaleFactor; + + WebView()->SetPageScaleFactor(kScaleFactor); + visual_viewport.Move(scroll_delta); + select->ShowPopup(); + + EXPECT_EQ(expected_x, Client().ShownBounds().x); + EXPECT_EQ(expected_y, Client().ShownBounds().y); +} + +TEST_F(ExternalPopupMenuTest, DidAcceptIndex) { + RegisterMockedURLLoad("select.html"); + LoadFrame("select.html"); + + HTMLSelectElement* select = ToHTMLSelectElement( + MainFrame()->GetFrame()->GetDocument()->getElementById("select")); + LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); + ASSERT_TRUE(menu_list); + + select->ShowPopup(); + ASSERT_TRUE(select->PopupIsVisible()); + + WebExternalPopupMenuClient* client = + static_cast<ExternalPopupMenu*>(select->Popup()); + client->DidAcceptIndex(2); + EXPECT_FALSE(select->PopupIsVisible()); + ASSERT_STREQ("2", menu_list->GetText().Utf8().data()); + EXPECT_EQ(2, select->selectedIndex()); +} + +TEST_F(ExternalPopupMenuTest, DidAcceptIndices) { + RegisterMockedURLLoad("select.html"); + LoadFrame("select.html"); + + HTMLSelectElement* select = ToHTMLSelectElement( + MainFrame()->GetFrame()->GetDocument()->getElementById("select")); + LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); + ASSERT_TRUE(menu_list); + + select->ShowPopup(); + ASSERT_TRUE(select->PopupIsVisible()); + + WebExternalPopupMenuClient* client = + static_cast<ExternalPopupMenu*>(select->Popup()); + int indices[] = {2}; + WebVector<int> indices_vector(indices, 1); + client->DidAcceptIndices(indices_vector); + EXPECT_FALSE(select->PopupIsVisible()); + EXPECT_STREQ("2", menu_list->GetText().Utf8().data()); + EXPECT_EQ(2, select->selectedIndex()); +} + +TEST_F(ExternalPopupMenuTest, DidAcceptIndicesClearSelect) { + RegisterMockedURLLoad("select.html"); + LoadFrame("select.html"); + + HTMLSelectElement* select = ToHTMLSelectElement( + MainFrame()->GetFrame()->GetDocument()->getElementById("select")); + LayoutMenuList* menu_list = ToLayoutMenuList(select->GetLayoutObject()); + ASSERT_TRUE(menu_list); + + select->ShowPopup(); + ASSERT_TRUE(select->PopupIsVisible()); + + WebExternalPopupMenuClient* client = + static_cast<ExternalPopupMenu*>(select->Popup()); + WebVector<int> indices; + client->DidAcceptIndices(indices); + EXPECT_FALSE(select->PopupIsVisible()); + EXPECT_EQ(-1, select->selectedIndex()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc new file mode 100644 index 00000000000..ebc3146a21d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/file_chooser.h" + +namespace blink { + +FileChooserClient::~FileChooserClient() = default; + +FileChooser* FileChooserClient::NewFileChooser( + const WebFileChooserParams& params) { + if (chooser_) + chooser_->DisconnectClient(); + + chooser_ = FileChooser::Create(this, params); + return chooser_.get(); +} + +inline FileChooser::FileChooser(FileChooserClient* client, + const WebFileChooserParams& params) + : client_(client), params_(params) {} + +scoped_refptr<FileChooser> FileChooser::Create( + FileChooserClient* client, + const WebFileChooserParams& params) { + return base::AdoptRef(new FileChooser(client, params)); +} + +FileChooser::~FileChooser() = default; + +void FileChooser::ChooseFiles(const Vector<FileChooserFileInfo>& files) { + // FIXME: This is inelegant. We should not be looking at params_ here. + if (params_.selected_files.size() == files.size()) { + bool was_changed = false; + for (unsigned i = 0; i < files.size(); ++i) { + if (String(params_.selected_files[i]) != files[i].path) { + was_changed = true; + break; + } + } + if (!was_changed) + return; + } + + if (client_) + client_->FilesChosen(files); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_chooser.h b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.h new file mode 100644 index 00000000000..b0d4ce85213 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/file_chooser.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_CHOOSER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_CHOOSER_H_ + +#include "third_party/blink/public/web/web_file_chooser_params.h" +#include "third_party/blink/renderer/platform/file_metadata.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/weborigin/kurl.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class FileChooser; + +struct FileChooserFileInfo { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + FileChooserFileInfo(const String& path, const String& display_name = String()) + : path(path), display_name(display_name) {} + + FileChooserFileInfo(const KURL& file_system_url, const FileMetadata metadata) + : file_system_url(file_system_url), metadata(metadata) {} + + // Members for native files. + const String path; + const String display_name; + + // Members for file system API files. + const KURL file_system_url; + const FileMetadata metadata; +}; + +class FileChooserClient : public GarbageCollectedMixin { + public: + virtual void FilesChosen(const Vector<FileChooserFileInfo>&) = 0; + virtual ~FileChooserClient(); + + protected: + FileChooser* NewFileChooser(const WebFileChooserParams&); + + private: + scoped_refptr<FileChooser> chooser_; +}; + +class FileChooser : public RefCounted<FileChooser> { + public: + static scoped_refptr<FileChooser> Create(FileChooserClient*, + const WebFileChooserParams&); + ~FileChooser(); + + void DisconnectClient() { client_ = nullptr; } + + // FIXME: We should probably just pass file paths that could be virtual paths + // with proper display names rather than passing structs. + void ChooseFiles(const Vector<FileChooserFileInfo>& files); + + const WebFileChooserParams& Params() const { return params_; } + + private: + FileChooser(FileChooserClient*, const WebFileChooserParams&); + + WeakPersistent<FileChooserClient> client_; + WebFileChooserParams params_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_CHOOSER_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc new file mode 100644 index 00000000000..ac9f83feee2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.cc @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + * rights reserved. + * Copyright (C) 2010 Google Inc. 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 "third_party/blink/renderer/core/html/forms/file_input_type.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/fileapi/file.h" +#include "third_party/blink/renderer/core/fileapi/file_list.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_file_upload_control.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/drag_data.h" +#include "third_party/blink/renderer/platform/file_metadata.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using blink::WebLocalizedString; +using namespace HTMLNames; + +namespace { + +WebVector<WebString> CollectAcceptTypes(const HTMLInputElement& input) { + Vector<String> mime_types = input.AcceptMIMETypes(); + Vector<String> extensions = input.AcceptFileExtensions(); + + Vector<String> accept_types; + accept_types.ReserveCapacity(mime_types.size() + extensions.size()); + accept_types.AppendVector(mime_types); + accept_types.AppendVector(extensions); + return accept_types; +} + +} // namespace + +inline FileInputType::FileInputType(HTMLInputElement& element) + : InputType(element), + KeyboardClickableInputTypeView(element), + file_list_(FileList::Create()) {} + +InputType* FileInputType::Create(HTMLInputElement& element) { + return new FileInputType(element); +} + +void FileInputType::Trace(blink::Visitor* visitor) { + visitor->Trace(file_list_); + KeyboardClickableInputTypeView::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* FileInputType::CreateView() { + return this; +} + +Vector<FileChooserFileInfo> FileInputType::FilesFromFormControlState( + const FormControlState& state) { + Vector<FileChooserFileInfo> files; + for (size_t i = 0; i < state.ValueSize(); i += 2) { + if (!state[i + 1].IsEmpty()) + files.push_back(FileChooserFileInfo(state[i], state[i + 1])); + else + files.push_back(FileChooserFileInfo(state[i])); + } + return files; +} + +const AtomicString& FileInputType::FormControlType() const { + return InputTypeNames::file; +} + +FormControlState FileInputType::SaveFormControlState() const { + if (file_list_->IsEmpty()) + return FormControlState(); + FormControlState state; + unsigned num_files = file_list_->length(); + for (unsigned i = 0; i < num_files; ++i) { + if (file_list_->item(i)->HasBackingFile()) { + state.Append(file_list_->item(i)->GetPath()); + state.Append(file_list_->item(i)->name()); + } + // FIXME: handle Blob-backed File instances, see http://crbug.com/394948 + } + return state; +} + +void FileInputType::RestoreFormControlState(const FormControlState& state) { + if (state.ValueSize() % 2) + return; + FilesChosen(FilesFromFormControlState(state)); +} + +void FileInputType::AppendToFormData(FormData& form_data) const { + FileList* file_list = GetElement().files(); + unsigned num_files = file_list->length(); + if (num_files == 0) { + form_data.append(GetElement().GetName(), File::Create("")); + return; + } + + for (unsigned i = 0; i < num_files; ++i) + form_data.append(GetElement().GetName(), file_list->item(i)); +} + +bool FileInputType::ValueMissing(const String& value) const { + return GetElement().IsRequired() && value.IsEmpty(); +} + +String FileInputType::ValueMissingText() const { + return GetLocale().QueryString( + GetElement().Multiple() + ? WebLocalizedString::kValidationValueMissingForMultipleFile + : WebLocalizedString::kValidationValueMissingForFile); +} + +void FileInputType::HandleDOMActivateEvent(Event* event) { + if (GetElement().IsDisabledFormControl()) + return; + + if (!Frame::HasTransientUserActivation(GetElement().GetDocument().GetFrame())) + return; + + if (ChromeClient* chrome_client = GetChromeClient()) { + WebFileChooserParams params; + HTMLInputElement& input = GetElement(); + Document& document = input.GetDocument(); + bool is_directory = input.FastHasAttribute(webkitdirectoryAttr); + params.directory = is_directory; + params.need_local_path = is_directory; + params.multi_select = is_directory || input.FastHasAttribute(multipleAttr); + params.accept_types = CollectAcceptTypes(input); + params.selected_files = file_list_->PathsForUserVisibleFiles(); + params.use_media_capture = RuntimeEnabledFeatures::MediaCaptureEnabled() && + input.FastHasAttribute(captureAttr); + params.requestor = document.Url(); + + UseCounter::Count( + document, document.IsSecureContext() + ? WebFeature::kInputTypeFileSecureOriginOpenChooser + : WebFeature::kInputTypeFileInsecureOriginOpenChooser); + + chrome_client->OpenFileChooser(document.GetFrame(), NewFileChooser(params)); + } + event->SetDefaultHandled(); +} + +LayoutObject* FileInputType::CreateLayoutObject(const ComputedStyle&) const { + return new LayoutFileUploadControl(&GetElement()); +} + +InputType::ValueMode FileInputType::GetValueMode() const { + return ValueMode::kFilename; +} + +bool FileInputType::CanSetStringValue() const { + return false; +} + +FileList* FileInputType::Files() { + return file_list_.Get(); +} + +bool FileInputType::CanSetValue(const String& value) { + // For security reasons, we don't allow setting the filename, but we do allow + // clearing it. The HTML5 spec (as of the 10/24/08 working draft) says that + // the value attribute isn't applicable to the file upload control at all, but + // for now we are keeping this behavior to avoid breaking existing websites + // that may be relying on this. + return value.IsEmpty(); +} + +String FileInputType::ValueInFilenameValueMode() const { + if (file_list_->IsEmpty()) + return String(); + + // HTML5 tells us that we're supposed to use this goofy value for + // file input controls. Historically, browsers revealed the real + // file path, but that's a privacy problem. Code on the web + // decided to try to parse the value by looking for backslashes + // (because that's what Windows file paths use). To be compatible + // with that code, we make up a fake path for the file. + return "C:\\fakepath\\" + file_list_->item(0)->name(); +} + +void FileInputType::SetValue(const String&, + bool value_changed, + TextFieldEventBehavior, + TextControlSetValueSelection) { + if (!value_changed) + return; + + file_list_->clear(); + GetElement().SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue)); + GetElement().SetNeedsValidityCheck(); +} + +FileList* FileInputType::CreateFileList( + const Vector<FileChooserFileInfo>& files, + bool has_webkit_directory_attr) { + FileList* file_list(FileList::Create()); + size_t size = files.size(); + + // If a directory is being selected, the UI allows a directory to be chosen + // and the paths provided here share a root directory somewhere up the tree; + // we want to store only the relative paths from that point. + if (size && has_webkit_directory_attr) { + // Find the common root path. + String root_path = DirectoryName(files[0].path); + for (size_t i = 1; i < size; ++i) { + while (!files[i].path.StartsWith(root_path)) + root_path = DirectoryName(root_path); + } + root_path = DirectoryName(root_path); + DCHECK(root_path.length()); + int root_length = root_path.length(); + if (root_path[root_length - 1] != '\\' && root_path[root_length - 1] != '/') + root_length += 1; + for (const auto& file : files) { + // Normalize backslashes to slashes before exposing the relative path to + // script. + String relative_path = + file.path.Substring(root_length).Replace('\\', '/'); + file_list->Append(File::CreateWithRelativePath(file.path, relative_path)); + } + return file_list; + } + + for (const auto& file : files) { + if (file.file_system_url.IsEmpty()) { + file_list->Append( + File::CreateForUserProvidedFile(file.path, file.display_name)); + } else { + file_list->Append(File::CreateForFileSystemFile( + file.file_system_url, file.metadata, File::kIsUserVisible)); + } + } + return file_list; +} + +void FileInputType::CountUsage() { + Document* document = &GetElement().GetDocument(); + if (document->IsSecureContext()) + UseCounter::Count(*document, WebFeature::kInputTypeFileInsecureOrigin); + else + UseCounter::Count(*document, WebFeature::kInputTypeFileSecureOrigin); +} + +void FileInputType::CreateShadowSubtree() { + DCHECK(IsShadowHost(GetElement())); + auto* button = HTMLInputElement::Create(GetElement().GetDocument(), + CreateElementFlags()); + button->setType(InputTypeNames::button); + button->setAttribute( + valueAttr, + AtomicString(GetLocale().QueryString( + GetElement().Multiple() + ? WebLocalizedString::kFileButtonChooseMultipleFilesLabel + : WebLocalizedString::kFileButtonChooseFileLabel))); + button->SetShadowPseudoId(AtomicString("-webkit-file-upload-button")); + GetElement().UserAgentShadowRoot()->AppendChild(button); +} + +void FileInputType::DisabledAttributeChanged() { + DCHECK(IsShadowHost(GetElement())); + if (Element* button = + ToElementOrDie(GetElement().UserAgentShadowRoot()->firstChild())) + button->SetBooleanAttribute(disabledAttr, + GetElement().IsDisabledFormControl()); +} + +void FileInputType::MultipleAttributeChanged() { + DCHECK(IsShadowHost(GetElement())); + if (Element* button = + ToElementOrDie(GetElement().UserAgentShadowRoot()->firstChild())) + button->setAttribute( + valueAttr, + AtomicString(GetLocale().QueryString( + GetElement().Multiple() + ? WebLocalizedString::kFileButtonChooseMultipleFilesLabel + : WebLocalizedString::kFileButtonChooseFileLabel))); +} + +void FileInputType::SetFiles(FileList* files) { + if (!files) + return; + + bool files_changed = false; + if (files->length() != file_list_->length()) { + files_changed = true; + } else { + for (unsigned i = 0; i < files->length(); ++i) { + if (!files->item(i)->HasSameSource(*file_list_->item(i))) { + files_changed = true; + break; + } + } + } + + file_list_ = files; + + GetElement().NotifyFormStateChanged(); + GetElement().SetNeedsValidityCheck(); + + if (GetElement().GetLayoutObject()) + GetElement().GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + + if (files_changed) { + // This call may cause destruction of this instance. + // input instance is safe since it is ref-counted. + GetElement().DispatchInputEvent(); + GetElement().DispatchChangeEvent(); + } +} + +void FileInputType::FilesChosen(const Vector<FileChooserFileInfo>& files) { + SetFiles(CreateFileList(files, + GetElement().FastHasAttribute(webkitdirectoryAttr))); +} + +void FileInputType::SetFilesFromDirectory(const String& path) { + if (ChromeClient* chrome_client = GetChromeClient()) { + Vector<String> files; + files.push_back(path); + WebFileChooserParams params; + params.directory = true; + params.multi_select = true; + params.selected_files = files; + params.accept_types = CollectAcceptTypes(GetElement()); + params.requestor = GetElement().GetDocument().Url(); + chrome_client->EnumerateChosenDirectory(NewFileChooser(params)); + } +} + +void FileInputType::SetFilesFromPaths(const Vector<String>& paths) { + if (paths.IsEmpty()) + return; + + HTMLInputElement& input = GetElement(); + if (input.FastHasAttribute(webkitdirectoryAttr)) { + SetFilesFromDirectory(paths[0]); + return; + } + + Vector<FileChooserFileInfo> files; + for (const auto& path : paths) + files.push_back(FileChooserFileInfo(path)); + + if (input.FastHasAttribute(multipleAttr)) { + FilesChosen(files); + } else { + Vector<FileChooserFileInfo> first_file_only; + first_file_only.push_back(files[0]); + FilesChosen(first_file_only); + } +} + +bool FileInputType::ReceiveDroppedFiles(const DragData* drag_data) { + Vector<String> paths; + drag_data->AsFilePaths(paths); + if (paths.IsEmpty()) + return false; + + if (!GetElement().FastHasAttribute(webkitdirectoryAttr)) { + dropped_file_system_id_ = drag_data->DroppedFileSystemId(); + } + SetFilesFromPaths(paths); + return true; +} + +String FileInputType::DroppedFileSystemId() { + return dropped_file_system_id_; +} + +String FileInputType::DefaultToolTip(const InputTypeView&) const { + FileList* file_list = file_list_.Get(); + unsigned list_size = file_list->length(); + if (!list_size) { + return GetLocale().QueryString( + WebLocalizedString::kFileButtonNoFileSelectedLabel); + } + + StringBuilder names; + for (size_t i = 0; i < list_size; ++i) { + names.Append(file_list->item(i)->name()); + if (i != list_size - 1) + names.Append('\n'); + } + return names.ToString(); +} + +void FileInputType::CopyNonAttributeProperties(const HTMLInputElement& source) { + DCHECK(file_list_->IsEmpty()); + const FileList* source_list = source.files(); + for (unsigned i = 0; i < source_list->length(); ++i) + file_list_->Append(source_list->item(i)->Clone()); +} + +void FileInputType::HandleKeypressEvent(KeyboardEvent* event) { + if (GetElement().FastHasAttribute(webkitdirectoryAttr)) { + // Override to invoke the action on Enter key up (not press) to avoid + // repeats committing the file chooser. + const String& key = event->key(); + if (key == "Enter") { + event->SetDefaultHandled(); + return; + } + } + KeyboardClickableInputTypeView::HandleKeypressEvent(event); +} + +void FileInputType::HandleKeyupEvent(KeyboardEvent* event) { + if (GetElement().FastHasAttribute(webkitdirectoryAttr)) { + // Override to invoke the action on Enter key up (not press) to avoid + // repeats committing the file chooser. + if (event->key() == "Enter") { + GetElement().DispatchSimulatedClick(event); + event->SetDefaultHandled(); + return; + } + } + KeyboardClickableInputTypeView::HandleKeyupEvent(event); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h new file mode 100644 index 00000000000..25aa374372f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_INPUT_TYPE_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/file_chooser.h" +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class DragData; +class FileList; + +class CORE_EXPORT FileInputType final : public InputType, + public KeyboardClickableInputTypeView, + private FileChooserClient { + USING_GARBAGE_COLLECTED_MIXIN(FileInputType); + + public: + static InputType* Create(HTMLInputElement&); + void Trace(blink::Visitor*) override; + using InputType::GetElement; + static Vector<FileChooserFileInfo> FilesFromFormControlState( + const FormControlState&); + static FileList* CreateFileList(const Vector<FileChooserFileInfo>& files, + bool has_webkit_directory_attr); + + void CountUsage() override; + + void SetFilesFromPaths(const Vector<String>&) override; + + private: + FileInputType(HTMLInputElement&); + InputTypeView* CreateView() override; + const AtomicString& FormControlType() const override; + FormControlState SaveFormControlState() const override; + void RestoreFormControlState(const FormControlState&) override; + void AppendToFormData(FormData&) const override; + bool ValueMissing(const String&) const override; + String ValueMissingText() const override; + void HandleDOMActivateEvent(Event*) override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + bool CanSetStringValue() const override; + FileList* Files() override; + void SetFiles(FileList*) override; + ValueMode GetValueMode() const override; + bool CanSetValue(const String&) override; + String ValueInFilenameValueMode() const override; + void SetValue(const String&, + bool value_changed, + TextFieldEventBehavior, + TextControlSetValueSelection) override; + bool ReceiveDroppedFiles(const DragData*) override; + String DroppedFileSystemId() override; + void CreateShadowSubtree() override; + void DisabledAttributeChanged() override; + void MultipleAttributeChanged() override; + String DefaultToolTip(const InputTypeView&) const override; + void CopyNonAttributeProperties(const HTMLInputElement&) override; + + // KeyboardClickableInputTypeView overrides. + void HandleKeypressEvent(KeyboardEvent*) override; + void HandleKeyupEvent(KeyboardEvent*) override; + + // FileChooserClient implementation. + void FilesChosen(const Vector<FileChooserFileInfo>&) override; + + void SetFilesFromDirectory(const String&); + + Member<FileList> file_list_; + String dropped_file_system_id_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FILE_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc b/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc new file mode 100644 index 00000000000..9ea88efd7b3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/file_input_type_test.cc @@ -0,0 +1,111 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/file_input_type.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/clipboard/data_object.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/fileapi/file_list.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/page/drag_data.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" + +namespace blink { + +TEST(FileInputTypeTest, createFileList) { + Vector<FileChooserFileInfo> files; + + // Native file. + files.push_back( + FileChooserFileInfo("/native/path/native-file", "display-name")); + + // Non-native file. + KURL url("filesystem:http://example.com/isolated/hash/non-native-file"); + FileMetadata metadata; + metadata.length = 64; + metadata.modification_time = 1.0 * kMsPerDay + 3; + files.push_back(FileChooserFileInfo(url, metadata)); + + FileList* list = FileInputType::CreateFileList(files, false); + ASSERT_TRUE(list); + ASSERT_EQ(2u, list->length()); + + EXPECT_EQ("/native/path/native-file", list->item(0)->GetPath()); + EXPECT_EQ("display-name", list->item(0)->name()); + EXPECT_TRUE(list->item(0)->FileSystemURL().IsEmpty()); + + EXPECT_TRUE(list->item(1)->GetPath().IsEmpty()); + EXPECT_EQ("non-native-file", list->item(1)->name()); + EXPECT_EQ(url, list->item(1)->FileSystemURL()); + EXPECT_EQ(64u, list->item(1)->size()); + EXPECT_EQ(1.0 * kMsPerDay + 3, list->item(1)->lastModified()); +} + +TEST(FileInputTypeTest, ignoreDroppedNonNativeFiles) { + Document* document = Document::CreateForTest(); + auto* input = HTMLInputElement::Create(*document, CreateElementFlags()); + InputType* file_input = FileInputType::Create(*input); + + DataObject* native_file_raw_drag_data = DataObject::Create(); + const DragData native_file_drag_data(native_file_raw_drag_data, IntPoint(), + IntPoint(), kDragOperationCopy); + native_file_drag_data.PlatformData()->Add(File::Create("/native/path")); + native_file_drag_data.PlatformData()->SetFilesystemId("fileSystemId"); + file_input->ReceiveDroppedFiles(&native_file_drag_data); + EXPECT_EQ("fileSystemId", file_input->DroppedFileSystemId()); + ASSERT_EQ(1u, file_input->Files()->length()); + EXPECT_EQ(String("/native/path"), file_input->Files()->item(0)->GetPath()); + + DataObject* non_native_file_raw_drag_data = DataObject::Create(); + const DragData non_native_file_drag_data(non_native_file_raw_drag_data, + IntPoint(), IntPoint(), + kDragOperationCopy); + FileMetadata metadata; + metadata.length = 1234; + const KURL url("filesystem:http://example.com/isolated/hash/non-native-file"); + non_native_file_drag_data.PlatformData()->Add( + File::CreateForFileSystemFile(url, metadata, File::kIsUserVisible)); + non_native_file_drag_data.PlatformData()->SetFilesystemId("fileSystemId"); + file_input->ReceiveDroppedFiles(&non_native_file_drag_data); + // Dropping non-native files should not change the existing files. + EXPECT_EQ("fileSystemId", file_input->DroppedFileSystemId()); + ASSERT_EQ(1u, file_input->Files()->length()); + EXPECT_EQ(String("/native/path"), file_input->Files()->item(0)->GetPath()); +} + +TEST(FileInputTypeTest, setFilesFromPaths) { + Document* document = Document::CreateForTest(); + auto* input = HTMLInputElement::Create(*document, CreateElementFlags()); + InputType* file_input = FileInputType::Create(*input); + Vector<String> paths; + paths.push_back("/native/path"); + paths.push_back("/native/path2"); + file_input->SetFilesFromPaths(paths); + ASSERT_EQ(1u, file_input->Files()->length()); + EXPECT_EQ(String("/native/path"), file_input->Files()->item(0)->GetPath()); + + // Try to upload multiple files without multipleAttr + paths.clear(); + paths.push_back("/native/path1"); + paths.push_back("/native/path2"); + file_input->SetFilesFromPaths(paths); + ASSERT_EQ(1u, file_input->Files()->length()); + EXPECT_EQ(String("/native/path1"), file_input->Files()->item(0)->GetPath()); + + // Try to upload multiple files with multipleAttr + input->SetBooleanAttribute(HTMLNames::multipleAttr, true); + paths.clear(); + paths.push_back("/native/real/path1"); + paths.push_back("/native/real/path2"); + file_input->SetFilesFromPaths(paths); + ASSERT_EQ(2u, file_input->Files()->length()); + EXPECT_EQ(String("/native/real/path1"), + file_input->Files()->item(0)->GetPath()); + EXPECT_EQ(String("/native/real/path2"), + file_input->Files()->item(1)->GetPath()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_associated.h b/chromium/third_party/blink/renderer/core/html/forms/form_associated.h new file mode 100644 index 00000000000..ee155b92cc9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_associated.h @@ -0,0 +1,22 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_ASSOCIATED_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_ASSOCIATED_H_ + +namespace blink { + +class HTMLFormElement; + +// Contains code to associate form with a form associated element +// https://html.spec.whatwg.org/multipage/forms.html#form-associated-element +class FormAssociated { + public: + // HTMLFormElement can be null + virtual void AssociateWith(HTMLFormElement*) = 0; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_ASSOCIATED_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc b/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc new file mode 100644 index 00000000000..acdb61a4eef --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_controller.cc @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010, 2011, 2012 Google Inc. 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 "third_party/blink/renderer/core/html/forms/form_controller.h" + +#include <memory> +#include <utility> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/html/forms/file_chooser.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/platform/wtf/deque.h" +#include "third_party/blink/renderer/platform/wtf/hash_table_deleted_value_type.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +using namespace HTMLNames; + +static inline HTMLFormElement* OwnerFormForState( + const HTMLFormControlElementWithState& control) { + // Assume controls with form attribute have no owners because we restore + // state during parsing and form owners of such controls might be + // indeterminate. + return control.FastHasAttribute(formAttr) ? nullptr : control.Form(); +} + +// ---------------------------------------------------------------------------- + +// Serilized form of FormControlState: +// (',' means strings around it are separated in stateVector.) +// +// SerializedControlState ::= SkipState | RestoreState +// SkipState ::= '0' +// RestoreState ::= UnsignedNumber, ControlValue+ +// UnsignedNumber ::= [0-9]+ +// ControlValue ::= arbitrary string +// +// RestoreState has a sequence of ControlValues. The length of the +// sequence is represented by UnsignedNumber. + +void FormControlState::SerializeTo(Vector<String>& state_vector) const { + DCHECK(!IsFailure()); + state_vector.push_back(String::Number(values_.size())); + for (const auto& value : values_) + state_vector.push_back(value.IsNull() ? g_empty_string : value); +} + +FormControlState FormControlState::Deserialize( + const Vector<String>& state_vector, + size_t& index) { + if (index >= state_vector.size()) + return FormControlState(kTypeFailure); + size_t value_size = state_vector[index++].ToUInt(); + if (!value_size) + return FormControlState(); + if (index + value_size > state_vector.size()) + return FormControlState(kTypeFailure); + FormControlState state; + state.values_.ReserveCapacity(value_size); + for (size_t i = 0; i < value_size; ++i) + state.Append(state_vector[index++]); + return state; +} + +// ---------------------------------------------------------------------------- + +class FormElementKey { + public: + FormElementKey(StringImpl* = nullptr, StringImpl* = nullptr); + ~FormElementKey(); + FormElementKey(const FormElementKey&); + FormElementKey& operator=(const FormElementKey&); + + StringImpl* GetName() const { return name_; } + StringImpl* GetType() const { return type_; } + + // Hash table deleted values, which are only constructed and never copied or + // destroyed. + FormElementKey(WTF::HashTableDeletedValueType) + : name_(HashTableDeletedValue()) {} + bool IsHashTableDeletedValue() const { + return name_ == HashTableDeletedValue(); + } + + private: + void Ref() const; + void Deref() const; + + static StringImpl* HashTableDeletedValue() { + return reinterpret_cast<StringImpl*>(-1); + } + + StringImpl* name_; + StringImpl* type_; +}; + +FormElementKey::FormElementKey(StringImpl* name, StringImpl* type) + : name_(name), type_(type) { + Ref(); +} + +FormElementKey::~FormElementKey() { + Deref(); +} + +FormElementKey::FormElementKey(const FormElementKey& other) + : name_(other.GetName()), type_(other.GetType()) { + Ref(); +} + +FormElementKey& FormElementKey::operator=(const FormElementKey& other) { + other.Ref(); + Deref(); + name_ = other.GetName(); + type_ = other.GetType(); + return *this; +} + +void FormElementKey::Ref() const { + if (GetName()) + GetName()->AddRef(); + if (GetType()) + GetType()->AddRef(); +} + +void FormElementKey::Deref() const { + if (GetName()) + GetName()->Release(); + if (GetType()) + GetType()->Release(); +} + +inline bool operator==(const FormElementKey& a, const FormElementKey& b) { + return a.GetName() == b.GetName() && a.GetType() == b.GetType(); +} + +struct FormElementKeyHash { + static unsigned GetHash(const FormElementKey&); + static bool Equal(const FormElementKey& a, const FormElementKey& b) { + return a == b; + } + static const bool safe_to_compare_to_empty_or_deleted = true; +}; + +unsigned FormElementKeyHash::GetHash(const FormElementKey& key) { + return StringHasher::HashMemory<sizeof(FormElementKey)>(&key); +} + +struct FormElementKeyHashTraits : WTF::GenericHashTraits<FormElementKey> { + static void ConstructDeletedValue(FormElementKey& slot, bool) { + new (NotNull, &slot) FormElementKey(WTF::kHashTableDeletedValue); + } + static bool IsDeletedValue(const FormElementKey& value) { + return value.IsHashTableDeletedValue(); + } +}; + +// ---------------------------------------------------------------------------- + +class SavedFormState { + USING_FAST_MALLOC(SavedFormState); + + public: + static std::unique_ptr<SavedFormState> Create(); + static std::unique_ptr<SavedFormState> Deserialize(const Vector<String>&, + size_t& index); + void SerializeTo(Vector<String>&) const; + bool IsEmpty() const { return state_for_new_form_elements_.IsEmpty(); } + void AppendControlState(const AtomicString& name, + const AtomicString& type, + const FormControlState&); + FormControlState TakeControlState(const AtomicString& name, + const AtomicString& type); + + Vector<String> GetReferencedFilePaths() const; + + private: + SavedFormState() : control_state_count_(0) {} + + using FormElementStateMap = HashMap<FormElementKey, + Deque<FormControlState>, + FormElementKeyHash, + FormElementKeyHashTraits>; + FormElementStateMap state_for_new_form_elements_; + size_t control_state_count_; + + DISALLOW_COPY_AND_ASSIGN(SavedFormState); +}; + +std::unique_ptr<SavedFormState> SavedFormState::Create() { + return base::WrapUnique(new SavedFormState); +} + +static bool IsNotFormControlTypeCharacter(UChar ch) { + return ch != '-' && (ch > 'z' || ch < 'a'); +} + +std::unique_ptr<SavedFormState> SavedFormState::Deserialize( + const Vector<String>& state_vector, + size_t& index) { + if (index >= state_vector.size()) + return nullptr; + // FIXME: We need String::toSizeT(). + size_t item_count = state_vector[index++].ToUInt(); + if (!item_count) + return nullptr; + std::unique_ptr<SavedFormState> saved_form_state = + base::WrapUnique(new SavedFormState); + while (item_count--) { + if (index + 1 >= state_vector.size()) + return nullptr; + String name = state_vector[index++]; + String type = state_vector[index++]; + FormControlState state = FormControlState::Deserialize(state_vector, index); + if (type.IsEmpty() || + type.Find(IsNotFormControlTypeCharacter) != kNotFound || + state.IsFailure()) + return nullptr; + saved_form_state->AppendControlState(AtomicString(name), AtomicString(type), + state); + } + return saved_form_state; +} + +void SavedFormState::SerializeTo(Vector<String>& state_vector) const { + state_vector.push_back(String::Number(control_state_count_)); + for (const auto& form_control : state_for_new_form_elements_) { + const FormElementKey& key = form_control.key; + const Deque<FormControlState>& queue = form_control.value; + for (const FormControlState& form_control_state : queue) { + state_vector.push_back(key.GetName()); + state_vector.push_back(key.GetType()); + form_control_state.SerializeTo(state_vector); + } + } +} + +void SavedFormState::AppendControlState(const AtomicString& name, + const AtomicString& type, + const FormControlState& state) { + FormElementKey key(name.Impl(), type.Impl()); + FormElementStateMap::iterator it = state_for_new_form_elements_.find(key); + if (it != state_for_new_form_elements_.end()) { + it->value.push_back(state); + } else { + Deque<FormControlState> state_list; + state_list.push_back(state); + state_for_new_form_elements_.Set(key, state_list); + } + control_state_count_++; +} + +FormControlState SavedFormState::TakeControlState(const AtomicString& name, + const AtomicString& type) { + if (state_for_new_form_elements_.IsEmpty()) + return FormControlState(); + FormElementStateMap::iterator it = state_for_new_form_elements_.find( + FormElementKey(name.Impl(), type.Impl())); + if (it == state_for_new_form_elements_.end()) + return FormControlState(); + DCHECK_GT(it->value.size(), 0u); + FormControlState state = it->value.TakeFirst(); + control_state_count_--; + if (!it->value.size()) + state_for_new_form_elements_.erase(it); + return state; +} + +Vector<String> SavedFormState::GetReferencedFilePaths() const { + Vector<String> to_return; + for (const auto& form_control : state_for_new_form_elements_) { + const FormElementKey& key = form_control.key; + if (!Equal(key.GetType(), "file", 4)) + continue; + const Deque<FormControlState>& queue = form_control.value; + for (const FormControlState& form_control_state : queue) { + const Vector<FileChooserFileInfo>& selected_files = + HTMLInputElement::FilesFromFileInputFormControlState( + form_control_state); + for (const auto& file : selected_files) + to_return.push_back(file.path); + } + } + return to_return; +} + +// ---------------------------------------------------------------------------- + +class FormKeyGenerator final + : public GarbageCollectedFinalized<FormKeyGenerator> { + + public: + static FormKeyGenerator* Create() { return new FormKeyGenerator; } + void Trace(blink::Visitor* visitor) { visitor->Trace(form_to_key_map_); } + const AtomicString& FormKey(const HTMLFormControlElementWithState&); + void WillDeleteForm(HTMLFormElement*); + + private: + FormKeyGenerator() = default; + + using FormToKeyMap = HeapHashMap<Member<HTMLFormElement>, AtomicString>; + using FormSignatureToNextIndexMap = HashMap<String, unsigned>; + FormToKeyMap form_to_key_map_; + FormSignatureToNextIndexMap form_signature_to_next_index_map_; + + DISALLOW_COPY_AND_ASSIGN(FormKeyGenerator); +}; + +static inline void RecordFormStructure(const HTMLFormElement& form, + StringBuilder& builder) { + // 2 is enough to distinguish forms in webkit.org/b/91209#c0 + const size_t kNamedControlsToBeRecorded = 2; + const ListedElement::List& controls = form.ListedElements(); + builder.Append(" ["); + for (size_t i = 0, named_controls = 0; + i < controls.size() && named_controls < kNamedControlsToBeRecorded; + ++i) { + if (!controls[i]->IsFormControlElementWithState()) + continue; + HTMLFormControlElementWithState* control = + ToHTMLFormControlElementWithState(controls[i]); + if (!OwnerFormForState(*control)) + continue; + AtomicString name = control->GetName(); + if (name.IsEmpty()) + continue; + named_controls++; + builder.Append(name); + builder.Append(' '); + } + builder.Append(']'); +} + +static inline String FormSignature(const HTMLFormElement& form) { + KURL action_url = form.GetURLAttribute(actionAttr); + // Remove the query part because it might contain volatile parameters such + // as a session key. + if (!action_url.IsEmpty()) + action_url.SetQuery(String()); + + StringBuilder builder; + if (!action_url.IsEmpty()) + builder.Append(action_url.GetString()); + + RecordFormStructure(form, builder); + return builder.ToString(); +} + +const AtomicString& FormKeyGenerator::FormKey( + const HTMLFormControlElementWithState& control) { + HTMLFormElement* form = OwnerFormForState(control); + if (!form) { + DEFINE_STATIC_LOCAL(const AtomicString, form_key_for_no_owner, + ("No owner")); + return form_key_for_no_owner; + } + FormToKeyMap::const_iterator it = form_to_key_map_.find(form); + if (it != form_to_key_map_.end()) + return it->value; + + String signature = FormSignature(*form); + DCHECK(!signature.IsNull()); + FormSignatureToNextIndexMap::AddResult result = + form_signature_to_next_index_map_.insert(signature, 0); + unsigned next_index = result.stored_value->value++; + + StringBuilder form_key_builder; + form_key_builder.Append(signature); + form_key_builder.Append(" #"); + form_key_builder.AppendNumber(next_index); + FormToKeyMap::AddResult add_form_keyresult = + form_to_key_map_.insert(form, form_key_builder.ToAtomicString()); + return add_form_keyresult.stored_value->value; +} + +void FormKeyGenerator::WillDeleteForm(HTMLFormElement* form) { + DCHECK(form); + form_to_key_map_.erase(form); +} + +// ---------------------------------------------------------------------------- + +DocumentState* DocumentState::Create() { + return new DocumentState; +} + +void DocumentState::Trace(blink::Visitor* visitor) { + visitor->Trace(form_controls_); +} + +void DocumentState::AddControl(HTMLFormControlElementWithState* control) { + DCHECK(!control->Next() && !control->Prev()); + form_controls_.Append(control); +} + +void DocumentState::RemoveControl(HTMLFormControlElementWithState* control) { + form_controls_.Remove(control); + control->SetPrev(nullptr); + control->SetNext(nullptr); +} + +static String FormStateSignature() { + // In the legacy version of serialized state, the first item was a name + // attribute value of a form control. The following string literal should + // contain some characters which are rarely used for name attribute values. + DEFINE_STATIC_LOCAL(String, signature, + ("\n\r?% Blink serialized form state version 9 \n\r=&")); + return signature; +} + +Vector<String> DocumentState::ToStateVector() { + FormKeyGenerator* key_generator = FormKeyGenerator::Create(); + std::unique_ptr<SavedFormStateMap> state_map = + base::WrapUnique(new SavedFormStateMap); + for (HTMLFormControlElementWithState* control = form_controls_.Head(); + control; control = control->Next()) { + DCHECK(control->isConnected()); + if (!control->ShouldSaveAndRestoreFormControlState()) + continue; + SavedFormStateMap::AddResult result = + state_map->insert(key_generator->FormKey(*control), nullptr); + if (result.is_new_entry) + result.stored_value->value = SavedFormState::Create(); + result.stored_value->value->AppendControlState( + control->GetName(), control->type(), control->SaveFormControlState()); + } + + Vector<String> state_vector; + state_vector.ReserveInitialCapacity(form_controls_.size() * 4); + state_vector.push_back(FormStateSignature()); + for (const auto& saved_form_state : *state_map) { + state_vector.push_back(saved_form_state.key); + saved_form_state.value->SerializeTo(state_vector); + } + bool has_only_signature = state_vector.size() == 1; + if (has_only_signature) + state_vector.clear(); + return state_vector; +} + +// ---------------------------------------------------------------------------- + +FormController::FormController() : document_state_(DocumentState::Create()) {} + +FormController::~FormController() = default; + +void FormController::Trace(blink::Visitor* visitor) { + visitor->Trace(document_state_); + visitor->Trace(form_key_generator_); +} + +DocumentState* FormController::FormElementsState() const { + return document_state_.Get(); +} + +void FormController::SetStateForNewFormElements( + const Vector<String>& state_vector) { + FormStatesFromStateVector(state_vector, saved_form_state_map_); +} + +bool FormController::HasFormStates() const { + return !saved_form_state_map_.IsEmpty(); +} + +FormControlState FormController::TakeStateForFormElement( + const HTMLFormControlElementWithState& control) { + if (saved_form_state_map_.IsEmpty()) + return FormControlState(); + if (!form_key_generator_) + form_key_generator_ = FormKeyGenerator::Create(); + SavedFormStateMap::iterator it = + saved_form_state_map_.find(form_key_generator_->FormKey(control)); + if (it == saved_form_state_map_.end()) + return FormControlState(); + FormControlState state = + it->value->TakeControlState(control.GetName(), control.type()); + if (it->value->IsEmpty()) + saved_form_state_map_.erase(it); + return state; +} + +void FormController::FormStatesFromStateVector( + const Vector<String>& state_vector, + SavedFormStateMap& map) { + map.clear(); + + size_t i = 0; + if (state_vector.size() < 1 || state_vector[i++] != FormStateSignature()) + return; + + while (i + 1 < state_vector.size()) { + AtomicString form_key = AtomicString(state_vector[i++]); + std::unique_ptr<SavedFormState> state = + SavedFormState::Deserialize(state_vector, i); + if (!state) { + i = 0; + break; + } + map.insert(form_key, std::move(state)); + } + if (i != state_vector.size()) + map.clear(); +} + +void FormController::WillDeleteForm(HTMLFormElement* form) { + if (form_key_generator_) + form_key_generator_->WillDeleteForm(form); +} + +void FormController::RestoreControlStateFor( + HTMLFormControlElementWithState& control) { + // We don't save state of a control with + // shouldSaveAndRestoreFormControlState() == false. But we need to skip + // restoring process too because a control in another form might have the same + // pair of name and type and saved its state. + if (!control.ShouldSaveAndRestoreFormControlState()) + return; + if (OwnerFormForState(control)) + return; + FormControlState state = TakeStateForFormElement(control); + if (state.ValueSize() > 0) + control.RestoreFormControlState(state); +} + +void FormController::RestoreControlStateIn(HTMLFormElement& form) { + EventQueueScope scope; + const ListedElement::List& elements = form.ListedElements(); + for (const auto& element : elements) { + if (!element->IsFormControlElementWithState()) + continue; + HTMLFormControlElementWithState* control = + ToHTMLFormControlElementWithState(element); + if (!control->ShouldSaveAndRestoreFormControlState()) + continue; + if (OwnerFormForState(*control) != &form) + continue; + FormControlState state = TakeStateForFormElement(*control); + if (state.ValueSize() > 0) { + // restoreFormControlState might dispatch input/change events. + control->RestoreFormControlState(state); + } + } +} + +Vector<String> FormController::GetReferencedFilePaths( + const Vector<String>& state_vector) { + Vector<String> to_return; + SavedFormStateMap map; + FormStatesFromStateVector(state_vector, map); + for (const auto& saved_form_state : map) + to_return.AppendVector(saved_form_state.value->GetReferencedFilePaths()); + return to_return; +} + +void FormController::RegisterStatefulFormControl( + HTMLFormControlElementWithState& control) { + document_state_->AddControl(&control); +} + +void FormController::UnregisterStatefulFormControl( + HTMLFormControlElementWithState& control) { + document_state_->RemoveControl(&control); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_controller.h b/chromium/third_party/blink/renderer/core/html/forms/form_controller.h new file mode 100644 index 00000000000..19c6a2c7e75 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_controller.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights + * reserved. + * Copyright (C) 2010, 2011, 2012 Google Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_CONTROLLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_CONTROLLER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/heap/heap_allocator.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class FormKeyGenerator; +class HTMLFormControlElementWithState; +class HTMLFormElement; +class SavedFormState; + +class FormControlState { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + FormControlState() : type_(kTypeSkip) {} + explicit FormControlState(const String& value) : type_(kTypeRestore) { + values_.push_back(value); + } + static FormControlState Deserialize(const Vector<String>& state_vector, + size_t& index); + FormControlState(const FormControlState& another) = default; + FormControlState& operator=(const FormControlState&); + + bool IsFailure() const { return type_ == kTypeFailure; } + size_t ValueSize() const { return values_.size(); } + const String& operator[](size_t i) const { return values_[i]; } + void Append(const String&); + void SerializeTo(Vector<String>& state_vector) const; + + private: + enum Type { kTypeSkip, kTypeRestore, kTypeFailure }; + explicit FormControlState(Type type) : type_(type) {} + + Type type_; + Vector<String> values_; +}; + +inline FormControlState& FormControlState::operator=( + const FormControlState& another) = default; + +inline void FormControlState::Append(const String& value) { + type_ = kTypeRestore; + values_.push_back(value); +} + +using SavedFormStateMap = + HashMap<AtomicString, std::unique_ptr<SavedFormState>>; + +class DocumentState final : public GarbageCollected<DocumentState> { + public: + static DocumentState* Create(); + void Trace(blink::Visitor*); + + void AddControl(HTMLFormControlElementWithState*); + void RemoveControl(HTMLFormControlElementWithState*); + Vector<String> ToStateVector(); + + private: + using FormElementList = HeapDoublyLinkedList<HTMLFormControlElementWithState>; + FormElementList form_controls_; +}; + +class FormController final : public GarbageCollectedFinalized<FormController> { + public: + static FormController* Create() { return new FormController; } + ~FormController(); + void Trace(blink::Visitor*); + + void RegisterStatefulFormControl(HTMLFormControlElementWithState&); + void UnregisterStatefulFormControl(HTMLFormControlElementWithState&); + // This should be callled only by Document::formElementsState(). + DocumentState* FormElementsState() const; + // This should be callled only by Document::setStateForNewFormElements(). + void SetStateForNewFormElements(const Vector<String>&); + // Returns true if saved state is set to this object and there are entries + // which are not consumed yet. + bool HasFormStates() const; + void WillDeleteForm(HTMLFormElement*); + void RestoreControlStateFor(HTMLFormControlElementWithState&); + void RestoreControlStateIn(HTMLFormElement&); + + static Vector<String> GetReferencedFilePaths( + const Vector<String>& state_vector); + + private: + FormController(); + FormControlState TakeStateForFormElement( + const HTMLFormControlElementWithState&); + static void FormStatesFromStateVector(const Vector<String>&, + SavedFormStateMap&); + + Member<DocumentState> document_state_; + SavedFormStateMap saved_form_state_map_; + Member<FormKeyGenerator> form_key_generator_; +}; + +} // namespace blink +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data.cc b/chromium/third_party/blink/renderer/core/html/forms/form_data.cc new file mode 100644 index 00000000000..877f254cc77 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.cc @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/form_data.h" + +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/fileapi/blob.h" +#include "third_party/blink/renderer/core/fileapi/file.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/platform/bindings/script_state.h" +#include "third_party/blink/renderer/platform/network/form_data_encoder.h" +#include "third_party/blink/renderer/platform/text/line_ending.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +namespace { + +class FormDataIterationSource final + : public PairIterable<String, FormDataEntryValue>::IterationSource { + public: + FormDataIterationSource(FormData* form_data) + : form_data_(form_data), current_(0) {} + + bool Next(ScriptState* script_state, + String& name, + FormDataEntryValue& value, + ExceptionState& exception_state) override { + if (current_ >= form_data_->size()) + return false; + + const FormData::Entry& entry = *form_data_->Entries()[current_++]; + name = form_data_->Decode(entry.name()); + if (entry.IsString()) { + value.SetUSVString(form_data_->Decode(entry.Value())); + } else { + DCHECK(entry.isFile()); + value.SetFile(entry.GetFile()); + } + return true; + } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(form_data_); + PairIterable<String, FormDataEntryValue>::IterationSource::Trace(visitor); + } + + private: + const Member<FormData> form_data_; + size_t current_; +}; + +} // namespace + +FormData::FormData(const WTF::TextEncoding& encoding) : encoding_(encoding) {} + +FormData::FormData(HTMLFormElement* form) : encoding_(UTF8Encoding()) { + if (form) + form->ConstructFormDataSet(nullptr, *this); +} + +void FormData::Trace(blink::Visitor* visitor) { + visitor->Trace(entries_); + ScriptWrappable::Trace(visitor); +} + +void FormData::append(const String& name, const String& value) { + entries_.push_back( + new Entry(EncodeAndNormalize(name), EncodeAndNormalize(value))); +} + +void FormData::append(ScriptState* script_state, + const String& name, + Blob* blob, + const String& filename) { + if (!blob) { + UseCounter::Count(ExecutionContext::From(script_state), + WebFeature::kFormDataAppendNull); + } + append(name, blob, filename); +} + +void FormData::deleteEntry(const String& name) { + const CString encoded_name = EncodeAndNormalize(name); + size_t i = 0; + while (i < entries_.size()) { + if (entries_[i]->name() == encoded_name) { + entries_.EraseAt(i); + } else { + ++i; + } + } +} + +void FormData::get(const String& name, FormDataEntryValue& result) { + const CString encoded_name = EncodeAndNormalize(name); + for (const auto& entry : Entries()) { + if (entry->name() == encoded_name) { + if (entry->IsString()) { + result.SetUSVString(Decode(entry->Value())); + } else { + DCHECK(entry->isFile()); + result.SetFile(entry->GetFile()); + } + return; + } + } +} + +HeapVector<FormDataEntryValue> FormData::getAll(const String& name) { + HeapVector<FormDataEntryValue> results; + + const CString encoded_name = EncodeAndNormalize(name); + for (const auto& entry : Entries()) { + if (entry->name() != encoded_name) + continue; + FormDataEntryValue value; + if (entry->IsString()) { + value.SetUSVString(Decode(entry->Value())); + } else { + DCHECK(entry->isFile()); + value.SetFile(entry->GetFile()); + } + results.push_back(value); + } + return results; +} + +bool FormData::has(const String& name) { + const CString encoded_name = EncodeAndNormalize(name); + for (const auto& entry : Entries()) { + if (entry->name() == encoded_name) + return true; + } + return false; +} + +void FormData::set(const String& name, const String& value) { + SetEntry(new Entry(EncodeAndNormalize(name), EncodeAndNormalize(value))); +} + +void FormData::set(const String& name, Blob* blob, const String& filename) { + SetEntry(new Entry(EncodeAndNormalize(name), blob, filename)); +} + +void FormData::SetEntry(const Entry* entry) { + DCHECK(entry); + const CString encoded_name = entry->name(); + bool found = false; + size_t i = 0; + while (i < entries_.size()) { + if (entries_[i]->name() != encoded_name) { + ++i; + } else if (found) { + entries_.EraseAt(i); + } else { + found = true; + entries_[i] = entry; + ++i; + } + } + if (!found) + entries_.push_back(entry); +} + +void FormData::append(const String& name, int value) { + append(name, String::Number(value)); +} + +void FormData::append(const String& name, Blob* blob, const String& filename) { + entries_.push_back(new Entry(EncodeAndNormalize(name), blob, filename)); +} + +CString FormData::EncodeAndNormalize(const String& string) const { + CString encoded_string = + encoding_.Encode(string, WTF::kEntitiesForUnencodables); + return NormalizeLineEndingsToCRLF(encoded_string); +} + +String FormData::Decode(const CString& data) const { + return Encoding().Decode(data.data(), data.length()); +} + +scoped_refptr<EncodedFormData> FormData::EncodeFormData( + EncodedFormData::EncodingType encoding_type) { + scoped_refptr<EncodedFormData> form_data = EncodedFormData::Create(); + Vector<char> encoded_data; + for (const auto& entry : Entries()) { + FormDataEncoder::AddKeyValuePairAsFormData( + encoded_data, entry->name(), + entry->isFile() ? EncodeAndNormalize(entry->GetFile()->name()) + : entry->Value(), + encoding_type); + } + form_data->AppendData(encoded_data.data(), encoded_data.size()); + return form_data; +} + +scoped_refptr<EncodedFormData> FormData::EncodeMultiPartFormData() { + scoped_refptr<EncodedFormData> form_data = EncodedFormData::Create(); + form_data->SetBoundary(FormDataEncoder::GenerateUniqueBoundaryString()); + Vector<char> encoded_data; + for (const auto& entry : Entries()) { + Vector<char> header; + FormDataEncoder::BeginMultiPartHeader(header, form_data->Boundary().data(), + entry->name()); + + // If the current type is blob, then we also need to include the + // filename. + if (entry->GetBlob()) { + String name; + if (entry->GetBlob()->IsFile()) { + File* file = ToFile(entry->GetBlob()); + // For file blob, use the filename (or relative path if it is + // present) as the name. + name = file->webkitRelativePath().IsEmpty() + ? file->name() + : file->webkitRelativePath(); + + // If a filename is passed in FormData.append(), use it instead + // of the file blob's name. + if (!entry->Filename().IsNull()) + name = entry->Filename(); + } else { + // For non-file blob, use the filename if it is passed in + // FormData.append(). + if (!entry->Filename().IsNull()) + name = entry->Filename(); + else + name = "blob"; + } + + // We have to include the filename=".." part in the header, even if + // the filename is empty. + FormDataEncoder::AddFilenameToMultiPartHeader(header, Encoding(), name); + + // Add the content type if available, or "application/octet-stream" + // otherwise (RFC 1867). + String content_type; + if (entry->GetBlob()->type().IsEmpty()) + content_type = "application/octet-stream"; + else + content_type = entry->GetBlob()->type(); + FormDataEncoder::AddContentTypeToMultiPartHeader(header, + content_type.Latin1()); + } + + FormDataEncoder::FinishMultiPartHeader(header); + + // Append body + form_data->AppendData(header.data(), header.size()); + if (entry->GetBlob()) { + if (entry->GetBlob()->HasBackingFile()) { + File* file = ToFile(entry->GetBlob()); + // Do not add the file if the path is empty. + if (!file->GetPath().IsEmpty()) + form_data->AppendFile(file->GetPath()); + } else { + form_data->AppendBlob(entry->GetBlob()->Uuid(), + entry->GetBlob()->GetBlobDataHandle()); + } + } else { + form_data->AppendData(entry->Value().data(), entry->Value().length()); + } + form_data->AppendData("\r\n", 2); + } + FormDataEncoder::AddBoundaryToMultiPartHeader( + encoded_data, form_data->Boundary().data(), true); + form_data->AppendData(encoded_data.data(), encoded_data.size()); + return form_data; +} + +PairIterable<String, FormDataEntryValue>::IterationSource* +FormData::StartIteration(ScriptState*, ExceptionState&) { + return new FormDataIterationSource(this); +} + +// ---------------------------------------------------------------- + +void FormData::Entry::Trace(blink::Visitor* visitor) { + visitor->Trace(blob_); +} + +File* FormData::Entry::GetFile() const { + DCHECK(GetBlob()); + // The spec uses the passed filename when inserting entries into the list. + // Here, we apply the filename (if present) as an override when extracting + // entries. + // FIXME: Consider applying the name during insertion. + + if (GetBlob()->IsFile()) { + File* file = ToFile(GetBlob()); + if (Filename().IsNull()) + return file; + return file->Clone(Filename()); + } + + String filename = filename_; + if (filename.IsNull()) + filename = "blob"; + return File::Create(filename, CurrentTimeMS(), + GetBlob()->GetBlobDataHandle()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data.h b/chromium/third_party/blink/renderer/core/html/forms/form_data.h new file mode 100644 index 00000000000..9e0ed37b2d2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_ + +#include "third_party/blink/renderer/bindings/core/v8/file_or_usv_string.h" +#include "third_party/blink/renderer/bindings/core/v8/iterable.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/network/encoded_form_data.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" + +namespace blink { + +class Blob; +class HTMLFormElement; +class ScriptState; + +// Typedef from FormData.idl: +typedef FileOrUSVString FormDataEntryValue; + +class CORE_EXPORT FormData final + : public ScriptWrappable, + public PairIterable<String, FormDataEntryValue> { + DEFINE_WRAPPERTYPEINFO(); + + public: + static FormData* Create(HTMLFormElement* form = nullptr) { + return new FormData(form); + } + + static FormData* Create(const WTF::TextEncoding& encoding) { + return new FormData(encoding); + } + void Trace(blink::Visitor*); + + // FormData IDL interface. + void append(const String& name, const String& value); + void append(ScriptState*, + const String& name, + Blob*, + const String& filename = String()); + void deleteEntry(const String& name); + void get(const String& name, FormDataEntryValue& result); + HeapVector<FormDataEntryValue> getAll(const String& name); + bool has(const String& name); + void set(const String& name, const String& value); + void set(const String& name, Blob*, const String& filename = String()); + + // Internal functions. + + const WTF::TextEncoding& Encoding() const { return encoding_; } + class Entry; + const HeapVector<Member<const Entry>>& Entries() const { return entries_; } + size_t size() const { return entries_.size(); } + void append(const String& name, int value); + void append(const String& name, Blob*, const String& filename = String()); + String Decode(const CString& data) const; + + // This flag is true if this FormData is created with a <form>, and its + // associated elements contain a non-empty password field. + bool ContainsPasswordData() const { return contains_password_data_; } + void SetContainsPasswordData(bool flag) { contains_password_data_ = flag; } + + scoped_refptr<EncodedFormData> EncodeFormData( + EncodedFormData::EncodingType = EncodedFormData::kFormURLEncoded); + scoped_refptr<EncodedFormData> EncodeMultiPartFormData(); + + private: + explicit FormData(const WTF::TextEncoding&); + explicit FormData(HTMLFormElement*); + void SetEntry(const Entry*); + CString EncodeAndNormalize(const String& key) const; + IterationSource* StartIteration(ScriptState*, ExceptionState&) override; + + WTF::TextEncoding encoding_; + // Entry pointers in m_entries never be nullptr. + HeapVector<Member<const Entry>> entries_; + bool contains_password_data_ = false; +}; + +// Represents entry, which is a pair of a name and a value. +// https://xhr.spec.whatwg.org/#concept-formdata-entry +// Entry objects are immutable. +class FormData::Entry : public GarbageCollectedFinalized<FormData::Entry> { + public: + Entry(const CString& name, const CString& value) + : name_(name), value_(value) {} + Entry(const CString& name, Blob* blob, const String& filename) + : name_(name), blob_(blob), filename_(filename) {} + void Trace(blink::Visitor*); + + bool IsString() const { return !blob_; } + bool isFile() const { return blob_; } + const CString& name() const { return name_; } + const CString& Value() const { return value_; } + Blob* GetBlob() const { return blob_.Get(); } + File* GetFile() const; + const String& Filename() const { return filename_; } + + private: + const CString name_; + const CString value_; + const Member<Blob> blob_; + const String filename_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data.idl b/chromium/third_party/blink/renderer/core/html/forms/form_data.idl new file mode 100644 index 00000000000..2fcaf4b3ce9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data.idl @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// https://xhr.spec.whatwg.org/#interface-formdata + +typedef (File or USVString) FormDataEntryValue; + +// TODO(foolip): Remove LegacyInterfaceTypeChecking, which allows for +// `append('name', null, 'filename')` and `set('name', null, 'filename')` to +// append/set null values instead of throwing. https://crbug.com/561338 +[ + Constructor(optional HTMLFormElement form), + Exposed=(Window,Worker), + LegacyInterfaceTypeChecking +] interface FormData { + void append(USVString name, USVString value); + [CallWith=ScriptState] void append(USVString name, Blob value, optional USVString filename); + [ImplementedAs=deleteEntry] void delete(USVString name); + FormDataEntryValue? get(USVString name); + sequence<FormDataEntryValue> getAll(USVString name); + boolean has(USVString name); + void set(USVString name, USVString value); + void set(USVString name, Blob value, optional USVString filename); + iterable<USVString, FormDataEntryValue>; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc new file mode 100644 index 00000000000..6fe89dd1a08 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.cc @@ -0,0 +1,28 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/form_data_event.h" + +#include "third_party/blink/renderer/core/html/forms/form_data.h" + +namespace blink { + +FormDataEvent::FormDataEvent(FormData& form_data) + : Event(EventTypeNames::formdata, Bubbles::kYes, Cancelable::kNo), + form_data_(form_data) {} + +FormDataEvent* FormDataEvent::Create(FormData& form_data) { + return new FormDataEvent(form_data); +} + +void FormDataEvent::Trace(Visitor* visitor) { + visitor->Trace(form_data_); + Event::Trace(visitor); +} + +const AtomicString& FormDataEvent::InterfaceName() const { + return EventNames::FormDataEvent; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.h b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.h new file mode 100644 index 00000000000..a3a31a506cd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.h @@ -0,0 +1,33 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_EVENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_EVENT_H_ + +#include "third_party/blink/renderer/core/dom/events/event.h" + +namespace blink { + +class FormData; + +class FormDataEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + static FormDataEvent* Create(FormData& form_data); + void Trace(Visitor* visitor) override; + + FormData* formData() const { return form_data_; }; + + const AtomicString& InterfaceName() const override; + + private: + FormDataEvent(FormData& form_data); + + Member<FormData> form_data_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_FORM_DATA_EVENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl new file mode 100644 index 00000000000..1b7c1bca272 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_event.idl @@ -0,0 +1,13 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://docs.google.com/document/d/1JO8puctCSpW-ZYGU8lF-h4FWRIDQNDVexzHoOQ2iQmY/edit?pli=1#heading=h.je8c7y5qpgki + +[ + Exposed=Window, + RuntimeEnabled=FormDataEvent +] +interface FormDataEvent : Event { + readonly attribute FormData formData; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc b/chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc new file mode 100644 index 00000000000..4b6ad726a37 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/form_data_test.cc @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/form_data.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +TEST(FormDataTest, get) { + FormData* fd = FormData::Create(UTF8Encoding()); + fd->append("name1", "value1"); + + FileOrUSVString result; + fd->get("name1", result); + EXPECT_TRUE(result.IsUSVString()); + EXPECT_EQ("value1", result.GetAsUSVString()); + + const FormData::Entry& entry = *fd->Entries()[0]; + EXPECT_STREQ("name1", entry.name().data()); + EXPECT_STREQ("value1", entry.Value().data()); +} + +TEST(FormDataTest, getAll) { + FormData* fd = FormData::Create(UTF8Encoding()); + fd->append("name1", "value1"); + + HeapVector<FormDataEntryValue> results = fd->getAll("name1"); + EXPECT_EQ(1u, results.size()); + EXPECT_TRUE(results[0].IsUSVString()); + EXPECT_EQ("value1", results[0].GetAsUSVString()); + + EXPECT_EQ(1u, fd->size()); +} + +TEST(FormDataTest, has) { + FormData* fd = FormData::Create(UTF8Encoding()); + fd->append("name1", "value1"); + + EXPECT_TRUE(fd->has("name1")); + EXPECT_EQ(1u, fd->size()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc new file mode 100644 index 00000000000..02bdd554181 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.cc @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/hidden_input_type.h" + +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" + +namespace blink { + +using namespace HTMLNames; + +InputType* HiddenInputType::Create(HTMLInputElement& element) { + return new HiddenInputType(element); +} + +void HiddenInputType::Trace(blink::Visitor* visitor) { + InputTypeView::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* HiddenInputType::CreateView() { + return this; +} + +const AtomicString& HiddenInputType::FormControlType() const { + return InputTypeNames::hidden; +} + +bool HiddenInputType::ShouldSaveAndRestoreFormControlState() const { + return false; +} + +bool HiddenInputType::SupportsValidation() const { + return false; +} + +LayoutObject* HiddenInputType::CreateLayoutObject(const ComputedStyle&) const { + NOTREACHED(); + return nullptr; +} + +void HiddenInputType::AccessKeyAction(bool) {} + +bool HiddenInputType::LayoutObjectIsNeeded() { + return false; +} + +InputType::ValueMode HiddenInputType::GetValueMode() const { + return ValueMode::kDefault; +} + +void HiddenInputType::SetValue(const String& sanitized_value, + bool, + TextFieldEventBehavior, + TextControlSetValueSelection) { + GetElement().setAttribute(valueAttr, AtomicString(sanitized_value)); +} + +void HiddenInputType::AppendToFormData(FormData& form_data) const { + if (DeprecatedEqualIgnoringCase(GetElement().GetName(), "_charset_")) { + form_data.append(GetElement().GetName(), + String(form_data.Encoding().GetName())); + return; + } + InputType::AppendToFormData(form_data); +} + +bool HiddenInputType::ShouldRespectHeightAndWidthAttributes() { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h new file mode 100644 index 00000000000..ba7b9fc22bb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/hidden_input_type.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HIDDEN_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HIDDEN_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/input_type_view.h" + +namespace blink { + +class HiddenInputType final : public InputType, private InputTypeView { + USING_GARBAGE_COLLECTED_MIXIN(HiddenInputType); + + public: + static InputType* Create(HTMLInputElement&); + void Trace(blink::Visitor*) override; + using InputType::GetElement; + + private: + HiddenInputType(HTMLInputElement& element) + : InputType(element), InputTypeView(element) {} + InputTypeView* CreateView() override; + const AtomicString& FormControlType() const override; + bool ShouldSaveAndRestoreFormControlState() const override; + bool SupportsValidation() const override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + void AccessKeyAction(bool send_mouse_events) override; + bool LayoutObjectIsNeeded() override; + ValueMode GetValueMode() const override; + bool IsInteractiveContent() const override { return false; } + bool ShouldRespectHeightAndWidthAttributes() override; + void SetValue(const String&, + bool, + TextFieldEventBehavior, + TextControlSetValueSelection) override; + void AppendToFormData(FormData&) const override; + bool NeedsShadowSubtree() const override { return false; } +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HIDDEN_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc new file mode 100644 index 00000000000..6e1ba1efce9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.cc @@ -0,0 +1,222 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) + * + * 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 "third_party/blink/renderer/core/html/forms/html_button_element.h" + +#include "third_party/blink/renderer/core/dom/attribute.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_button.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" + +namespace blink { + +using namespace HTMLNames; + +inline HTMLButtonElement::HTMLButtonElement(Document& document) + : HTMLFormControlElement(buttonTag, document), + type_(SUBMIT), + is_activated_submit_(false) {} + +HTMLButtonElement* HTMLButtonElement::Create(Document& document) { + return new HTMLButtonElement(document); +} + +void HTMLButtonElement::setType(const AtomicString& type) { + setAttribute(typeAttr, type); +} + +LayoutObject* HTMLButtonElement::CreateLayoutObject(const ComputedStyle&) { + return new LayoutButton(this); +} + +const AtomicString& HTMLButtonElement::FormControlType() const { + switch (type_) { + case SUBMIT: { + DEFINE_STATIC_LOCAL(const AtomicString, submit, ("submit")); + return submit; + } + case BUTTON: { + DEFINE_STATIC_LOCAL(const AtomicString, button, ("button")); + return button; + } + case RESET: { + DEFINE_STATIC_LOCAL(const AtomicString, reset, ("reset")); + return reset; + } + } + + NOTREACHED(); + return g_empty_atom; +} + +bool HTMLButtonElement::IsPresentationAttribute( + const QualifiedName& name) const { + if (name == alignAttr) { + // Don't map 'align' attribute. This matches what Firefox and IE do, but + // not Opera. See http://bugs.webkit.org/show_bug.cgi?id=12071 + return false; + } + + return HTMLFormControlElement::IsPresentationAttribute(name); +} + +void HTMLButtonElement::ParseAttribute( + const AttributeModificationParams& params) { + if (params.name == typeAttr) { + if (DeprecatedEqualIgnoringCase(params.new_value, "reset")) + type_ = RESET; + else if (DeprecatedEqualIgnoringCase(params.new_value, "button")) + type_ = BUTTON; + else + type_ = SUBMIT; + SetNeedsWillValidateCheck(); + if (formOwner() && isConnected()) + formOwner()->InvalidateDefaultButtonStyle(); + } else { + if (params.name == formactionAttr) + LogUpdateAttributeIfIsolatedWorldAndInDocument("button", params); + HTMLFormControlElement::ParseAttribute(params); + } +} + +void HTMLButtonElement::DefaultEventHandler(Event* event) { + if (event->type() == EventTypeNames::DOMActivate && + !IsDisabledFormControl()) { + if (Form() && type_ == SUBMIT) { + Form()->PrepareForSubmission(event, this); + event->SetDefaultHandled(); + } + if (Form() && type_ == RESET) { + Form()->reset(); + event->SetDefaultHandled(); + } + } + + if (event->IsKeyboardEvent()) { + if (event->type() == EventTypeNames::keydown && + ToKeyboardEvent(event)->key() == " ") { + SetActive(true); + // No setDefaultHandled() - IE dispatches a keypress in this case. + return; + } + if (event->type() == EventTypeNames::keypress) { + switch (ToKeyboardEvent(event)->charCode()) { + case '\r': + DispatchSimulatedClick(event); + event->SetDefaultHandled(); + return; + case ' ': + // Prevent scrolling down the page. + event->SetDefaultHandled(); + return; + } + } + if (event->type() == EventTypeNames::keyup && + ToKeyboardEvent(event)->key() == " ") { + if (IsActive()) + DispatchSimulatedClick(event); + event->SetDefaultHandled(); + return; + } + } + + HTMLFormControlElement::DefaultEventHandler(event); +} + +bool HTMLButtonElement::HasActivationBehavior() const { + return true; +} + +bool HTMLButtonElement::WillRespondToMouseClickEvents() { + if (!IsDisabledFormControl() && Form() && (type_ == SUBMIT || type_ == RESET)) + return true; + return HTMLFormControlElement::WillRespondToMouseClickEvents(); +} + +bool HTMLButtonElement::CanBeSuccessfulSubmitButton() const { + return type_ == SUBMIT; +} + +bool HTMLButtonElement::IsActivatedSubmit() const { + return is_activated_submit_; +} + +void HTMLButtonElement::SetActivatedSubmit(bool flag) { + is_activated_submit_ = flag; +} + +void HTMLButtonElement::AppendToFormData(FormData& form_data) { + if (type_ == SUBMIT && !GetName().IsEmpty() && is_activated_submit_) + form_data.append(GetName(), Value()); +} + +void HTMLButtonElement::AccessKeyAction(bool send_mouse_events) { + focus(); + + DispatchSimulatedClick( + nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents); +} + +bool HTMLButtonElement::IsURLAttribute(const Attribute& attribute) const { + return attribute.GetName() == formactionAttr || + HTMLFormControlElement::IsURLAttribute(attribute); +} + +const AtomicString& HTMLButtonElement::Value() const { + return getAttribute(valueAttr); +} + +bool HTMLButtonElement::RecalcWillValidate() const { + return type_ == SUBMIT && HTMLFormControlElement::RecalcWillValidate(); +} + +bool HTMLButtonElement::IsInteractiveContent() const { + return true; +} + +bool HTMLButtonElement::SupportsAutofocus() const { + return true; +} + +bool HTMLButtonElement::MatchesDefaultPseudoClass() const { + // HTMLFormElement::findDefaultButton() traverses the tree. So we check + // canBeSuccessfulSubmitButton() first for early return. + return CanBeSuccessfulSubmitButton() && Form() && + Form()->FindDefaultButton() == this; +} + +Node::InsertionNotificationRequest HTMLButtonElement::InsertedInto( + ContainerNode* insertion_point) { + InsertionNotificationRequest request = + HTMLFormControlElement::InsertedInto(insertion_point); + LogAddElementIfIsolatedWorldAndInDocument("button", typeAttr, formmethodAttr, + formactionAttr); + return request; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h new file mode 100644 index 00000000000..23da1dfd32d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" + +namespace blink { + +class HTMLButtonElement final : public HTMLFormControlElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLButtonElement* Create(Document&); + + void setType(const AtomicString&); + + const AtomicString& Value() const; + + bool WillRespondToMouseClickEvents() override; + + private: + explicit HTMLButtonElement(Document&); + + enum Type { SUBMIT, RESET, BUTTON }; + + const AtomicString& FormControlType() const override; + + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + + // HTMLFormControlElement always creates one, but buttons don't need it. + bool AlwaysCreateUserAgentShadowRoot() const override { return false; } + + Node::InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void ParseAttribute(const AttributeModificationParams&) override; + bool IsPresentationAttribute(const QualifiedName&) const override; + void DefaultEventHandler(Event*) override; + bool HasActivationBehavior() const override; + + void AppendToFormData(FormData&) override; + + bool IsEnumeratable() const override { return true; } + bool SupportLabels() const override { return true; } + bool ShouldForceLegacyLayout() const final { return true; } + bool IsInteractiveContent() const override; + bool SupportsAutofocus() const override; + bool MatchesDefaultPseudoClass() const override; + + bool CanBeSuccessfulSubmitButton() const override; + bool IsActivatedSubmit() const override; + void SetActivatedSubmit(bool flag) override; + + void AccessKeyAction(bool send_mouse_events) override; + bool IsURLAttribute(const Attribute&) const override; + + bool CanStartSelection() const override { return false; } + + bool IsOptionalFormControl() const override { return true; } + bool RecalcWillValidate() const override; + + Type type_; + bool is_activated_submit_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl new file mode 100644 index 00000000000..a56286740c9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_button_element.idl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * + * 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. + */ + +// https://html.spec.whatwg.org/#the-button-element +[HTMLConstructor] +interface HTMLButtonElement : HTMLElement { + [CEReactions, Reflect] attribute boolean autofocus; + [CEReactions, Reflect] attribute boolean disabled; + [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form; + [CEReactions] attribute DOMString formAction; + [CEReactions] attribute DOMString formEnctype; + [CEReactions] attribute DOMString formMethod; + [CEReactions, Reflect] attribute boolean formNoValidate; + [CEReactions, Reflect] attribute DOMString formTarget; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString value; + // FIXME: attribute HTMLMenuElement? menu; + + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); + + readonly attribute NodeList labels; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc new file mode 100644 index 00000000000..7ded38a407f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.cc @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" + +#include "third_party/blink/renderer/core/dom/id_target_observer_registry.h" +#include "third_party/blink/renderer/core/dom/node_lists_node_data.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +inline HTMLDataListElement::HTMLDataListElement(Document& document) + : HTMLElement(HTMLNames::datalistTag, document) {} + +HTMLDataListElement* HTMLDataListElement::Create(Document& document) { + UseCounter::Count(document, WebFeature::kDataListElement); + return new HTMLDataListElement(document); +} + +HTMLDataListOptionsCollection* HTMLDataListElement::options() { + return EnsureCachedCollection<HTMLDataListOptionsCollection>( + kDataListOptions); +} + +void HTMLDataListElement::ChildrenChanged(const ChildrenChange& change) { + HTMLElement::ChildrenChanged(change); + if (!change.by_parser) { + GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers( + GetIdAttribute()); + } +} + +void HTMLDataListElement::FinishParsingChildren() { + GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers( + GetIdAttribute()); +} + +void HTMLDataListElement::OptionElementChildrenChanged() { + GetTreeScope().GetIdTargetObserverRegistry().NotifyObservers( + GetIdAttribute()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h new file mode 100644 index 00000000000..abb676f53ae --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009, Google Inc. All rights reserved. + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class HTMLDataListOptionsCollection; + +class CORE_EXPORT HTMLDataListElement final : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLDataListElement* Create(Document&); + + HTMLDataListOptionsCollection* options(); + + void OptionElementChildrenChanged(); + + private: + HTMLDataListElement(Document&); + void ChildrenChanged(const ChildrenChange&) override; + void FinishParsingChildren() override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl new file mode 100644 index 00000000000..46de4a0a335 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_element.idl @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// https://html.spec.whatwg.org/#the-datalist-element +[HTMLConstructor] +interface HTMLDataListElement : HTMLElement { + readonly attribute HTMLCollection options; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h new file mode 100644 index 00000000000..cc7a21b476f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_OPTIONS_COLLECTION_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_OPTIONS_COLLECTION_H_ + +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/html_collection.h" + +namespace blink { + +class HTMLDataListOptionsCollection : public HTMLCollection { + public: + static HTMLDataListOptionsCollection* Create(ContainerNode& owner_node, + CollectionType type) { + DCHECK_EQ(type, kDataListOptions); + return new HTMLDataListOptionsCollection(owner_node); + } + + HTMLOptionElement* Item(unsigned offset) const { + return ToHTMLOptionElement(HTMLCollection::item(offset)); + } + + bool ElementMatches(const HTMLElement&) const; + + private: + explicit HTMLDataListOptionsCollection(ContainerNode& owner_node) + : HTMLCollection(owner_node, + kDataListOptions, + kDoesNotOverrideItemAfter) {} +}; + +DEFINE_TYPE_CASTS(HTMLDataListOptionsCollection, + LiveNodeListBase, + collection, + collection->GetType() == kDataListOptions, + collection.GetType() == kDataListOptions); + +inline bool HTMLDataListOptionsCollection::ElementMatches( + const HTMLElement& element) const { + return IsHTMLOptionElement(element); +} + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_DATA_LIST_OPTIONS_COLLECTION_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc new file mode 100644 index 00000000000..34b273b7a15 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.cc @@ -0,0 +1,134 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_field_set_element.h" + +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/dom/node_lists_node_data.h" +#include "third_party/blink/renderer/core/html/forms/html_legend_element.h" +#include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_fieldset.h" +#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" + +namespace blink { + +using namespace HTMLNames; + +inline HTMLFieldSetElement::HTMLFieldSetElement(Document& document) + : HTMLFormControlElement(fieldsetTag, document) {} + +HTMLFieldSetElement* HTMLFieldSetElement::Create(Document& document) { + return new HTMLFieldSetElement(document); +} + +bool HTMLFieldSetElement::MatchesValidityPseudoClasses() const { + return true; +} + +bool HTMLFieldSetElement::IsValidElement() { + for (Element* element : *elements()) { + if (element->IsFormControlElement()) { + if (!ToHTMLFormControlElement(element)->checkValidity( + nullptr, kCheckValidityDispatchNoEvent)) + return false; + } + } + return true; +} + +bool HTMLFieldSetElement::IsSubmittableElement() { + return false; +} + +// Returns a disabled focused element if it's in descendants of |base|. +Element* +HTMLFieldSetElement::InvalidateDescendantDisabledStateAndFindFocusedOne( + Element& base) { + Element* focused_element = AdjustedFocusedElementInTreeScope(); + bool should_blur = false; + { + EventDispatchForbiddenScope event_forbidden; + for (HTMLFormControlElement& element : + Traversal<HTMLFormControlElement>::DescendantsOf(base)) { + element.AncestorDisabledStateWasChanged(); + if (focused_element == &element && element.IsDisabledFormControl()) + should_blur = true; + } + } + return should_blur ? focused_element : nullptr; +} + +void HTMLFieldSetElement::DisabledAttributeChanged() { + // This element must be updated before the style of nodes in its subtree gets + // recalculated. + HTMLFormControlElement::DisabledAttributeChanged(); + if (Element* focused_element = + InvalidateDescendantDisabledStateAndFindFocusedOne(*this)) + focused_element->blur(); +} + +void HTMLFieldSetElement::ChildrenChanged(const ChildrenChange& change) { + HTMLFormControlElement::ChildrenChanged(change); + Element* focused_element = nullptr; + { + EventDispatchForbiddenScope event_forbidden; + for (HTMLLegendElement& legend : + Traversal<HTMLLegendElement>::ChildrenOf(*this)) { + if (Element* element = + InvalidateDescendantDisabledStateAndFindFocusedOne(legend)) + focused_element = element; + } + } + if (focused_element) + focused_element->blur(); +} + +bool HTMLFieldSetElement::SupportsFocus() const { + return HTMLElement::SupportsFocus() && !IsDisabledFormControl(); +} + +const AtomicString& HTMLFieldSetElement::FormControlType() const { + DEFINE_STATIC_LOCAL(const AtomicString, fieldset, ("fieldset")); + return fieldset; +} + +LayoutObject* HTMLFieldSetElement::CreateLayoutObject(const ComputedStyle&) { + return new LayoutFieldset(this); +} + +HTMLLegendElement* HTMLFieldSetElement::Legend() const { + return Traversal<HTMLLegendElement>::FirstChild(*this); +} + +HTMLCollection* HTMLFieldSetElement::elements() { + return EnsureCachedCollection<HTMLCollection>(kFormControls); +} + +int HTMLFieldSetElement::tabIndex() const { + return HTMLElement::tabIndex(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h new file mode 100644 index 00000000000..3020e62468a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FIELD_SET_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FIELD_SET_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" + +namespace blink { + +class HTMLCollection; + +class CORE_EXPORT HTMLFieldSetElement final : public HTMLFormControlElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLFieldSetElement* Create(Document&); + HTMLLegendElement* Legend() const; + HTMLCollection* elements(); + + protected: + void DisabledAttributeChanged() override; + + private: + explicit HTMLFieldSetElement(Document&); + + bool IsEnumeratable() const override { return true; } + bool SupportsFocus() const override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + const AtomicString& FormControlType() const override; + bool RecalcWillValidate() const override { return false; } + int tabIndex() const final; + bool MatchesValidityPseudoClasses() const final; + bool IsValidElement() final; + void ChildrenChanged(const ChildrenChange&) override; + bool AreAuthorShadowsAllowed() const override { return false; } + bool IsSubmittableElement() override; + bool AlwaysCreateUserAgentShadowRoot() const override { return false; } + + Element* InvalidateDescendantDisabledStateAndFindFocusedOne(Element& base); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FIELD_SET_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl new file mode 100644 index 00000000000..69b3aff69ec --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_field_set_element.idl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * 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. + */ + +// https://html.spec.whatwg.org/#the-fieldset-element +[HTMLConstructor] +interface HTMLFieldSetElement : HTMLElement { + [CEReactions, Reflect] attribute boolean disabled; + [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form; + [CEReactions, Reflect] attribute DOMString name; + + readonly attribute DOMString type; + + [Measure] readonly attribute HTMLCollection elements; + + readonly attribute boolean willValidate; + [SameObject] readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc new file mode 100644 index 00000000000..1a3f8837b09 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.cc @@ -0,0 +1,682 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_form_control_element.h" + +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" +#include "third_party/blink/renderer/core/html/forms/html_field_set_element.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/html_legend_element.h" +#include "third_party/blink/renderer/core/html/forms/validity_state.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/page/validation_message_client.h" +#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h" +#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" +#include "third_party/blink/renderer/platform/text/bidi_text_run.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +using namespace HTMLNames; + +HTMLFormControlElement::HTMLFormControlElement(const QualifiedName& tag_name, + Document& document) + : LabelableElement(tag_name, document), + ancestor_disabled_state_(kAncestorDisabledStateUnknown), + data_list_ancestor_state_(kUnknown), + may_have_field_set_ancestor_(true), + is_autofilled_(false), + has_validation_message_(false), + will_validate_initialized_(false), + will_validate_(true), + is_valid_(true), + validity_is_dirty_(false), + blocks_form_submission_(false) { + SetHasCustomStyleCallbacks(); +} + +HTMLFormControlElement::~HTMLFormControlElement() = default; + +void HTMLFormControlElement::Trace(blink::Visitor* visitor) { + ListedElement::Trace(visitor); + LabelableElement::Trace(visitor); +} + +String HTMLFormControlElement::formAction() const { + const AtomicString& action = FastGetAttribute(formactionAttr); + if (action.IsEmpty()) + return GetDocument().Url(); + return GetDocument().CompleteURL(StripLeadingAndTrailingHTMLSpaces(action)); +} + +void HTMLFormControlElement::setFormAction(const AtomicString& value) { + setAttribute(formactionAttr, value); +} + +String HTMLFormControlElement::formEnctype() const { + const AtomicString& form_enctype_attr = FastGetAttribute(formenctypeAttr); + if (form_enctype_attr.IsNull()) + return g_empty_string; + return FormSubmission::Attributes::ParseEncodingType(form_enctype_attr); +} + +void HTMLFormControlElement::setFormEnctype(const AtomicString& value) { + setAttribute(formenctypeAttr, value); +} + +String HTMLFormControlElement::formMethod() const { + const AtomicString& form_method_attr = FastGetAttribute(formmethodAttr); + if (form_method_attr.IsNull()) + return g_empty_string; + return FormSubmission::Attributes::MethodString( + FormSubmission::Attributes::ParseMethodType(form_method_attr)); +} + +void HTMLFormControlElement::setFormMethod(const AtomicString& value) { + setAttribute(formmethodAttr, value); +} + +bool HTMLFormControlElement::FormNoValidate() const { + return FastHasAttribute(formnovalidateAttr); +} + +void HTMLFormControlElement::UpdateAncestorDisabledState() const { + if (!may_have_field_set_ancestor_) { + ancestor_disabled_state_ = kAncestorDisabledStateEnabled; + return; + } + may_have_field_set_ancestor_ = false; + // <fieldset> element of which |disabled| attribute affects |this| element. + HTMLFieldSetElement* disabled_fieldset_ancestor = nullptr; + ContainerNode* last_legend_ancestor = nullptr; + for (HTMLElement* ancestor = Traversal<HTMLElement>::FirstAncestor(*this); + ancestor; ancestor = Traversal<HTMLElement>::FirstAncestor(*ancestor)) { + if (IsHTMLLegendElement(*ancestor)) + last_legend_ancestor = ancestor; + if (IsHTMLFieldSetElement(*ancestor)) { + may_have_field_set_ancestor_ = true; + if (ancestor->IsDisabledFormControl()) { + auto* fieldset = ToHTMLFieldSetElement(ancestor); + if (last_legend_ancestor && last_legend_ancestor == fieldset->Legend()) + continue; + disabled_fieldset_ancestor = fieldset; + break; + } + } + } + ancestor_disabled_state_ = disabled_fieldset_ancestor + ? kAncestorDisabledStateDisabled + : kAncestorDisabledStateEnabled; +} + +void HTMLFormControlElement::AncestorDisabledStateWasChanged() { + ancestor_disabled_state_ = kAncestorDisabledStateUnknown; + DisabledAttributeChanged(); +} + +void HTMLFormControlElement::Reset() { + SetAutofilled(false); + ResetImpl(); +} + +void HTMLFormControlElement::AttributeChanged( + const AttributeModificationParams& params) { + HTMLElement::AttributeChanged(params); + if (params.name == disabledAttr && + params.old_value.IsNull() != params.new_value.IsNull()) { + DisabledAttributeChanged(); + if (params.reason == AttributeModificationReason::kDirectly && + IsDisabledFormControl() && AdjustedFocusedElementInTreeScope() == this) + blur(); + } +} + +void HTMLFormControlElement::ParseAttribute( + const AttributeModificationParams& params) { + const QualifiedName& name = params.name; + if (name == formAttr) { + FormAttributeChanged(); + UseCounter::Count(GetDocument(), WebFeature::kFormAttribute); + } else if (name == readonlyAttr) { + if (params.old_value.IsNull() != params.new_value.IsNull()) { + SetNeedsWillValidateCheck(); + PseudoStateChanged(CSSSelector::kPseudoReadOnly); + PseudoStateChanged(CSSSelector::kPseudoReadWrite); + if (LayoutObject* o = GetLayoutObject()) + o->InvalidateIfControlStateChanged(kReadOnlyControlState); + } + } else if (name == requiredAttr) { + if (params.old_value.IsNull() != params.new_value.IsNull()) + RequiredAttributeChanged(); + UseCounter::Count(GetDocument(), WebFeature::kRequiredAttribute); + } else if (name == autofocusAttr) { + HTMLElement::ParseAttribute(params); + UseCounter::Count(GetDocument(), WebFeature::kAutoFocusAttribute); + } else { + HTMLElement::ParseAttribute(params); + } +} + +void HTMLFormControlElement::DisabledAttributeChanged() { + // Don't blur in this function because this is called for descendants of + // <fieldset> while tree traversal. + EventDispatchForbiddenScope event_forbidden; + + SetNeedsWillValidateCheck(); + PseudoStateChanged(CSSSelector::kPseudoDisabled); + PseudoStateChanged(CSSSelector::kPseudoEnabled); + if (LayoutObject* o = GetLayoutObject()) + o->InvalidateIfControlStateChanged(kEnabledControlState); + + // TODO(dmazzoni): http://crbug.com/699438. + // Replace |CheckedStateChanged| with a generic tree changed event. + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) + cache->CheckedStateChanged(this); +} + +void HTMLFormControlElement::RequiredAttributeChanged() { + SetNeedsValidityCheck(); + PseudoStateChanged(CSSSelector::kPseudoRequired); + PseudoStateChanged(CSSSelector::kPseudoOptional); + // TODO(dmazzoni): http://crbug.com/699438. + // Replace |CheckedStateChanged| with a generic tree changed event. + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) + cache->CheckedStateChanged(this); +} + +bool HTMLFormControlElement::IsReadOnly() const { + return FastHasAttribute(HTMLNames::readonlyAttr); +} + +bool HTMLFormControlElement::IsDisabledOrReadOnly() const { + return IsDisabledFormControl() || IsReadOnly(); +} + +bool HTMLFormControlElement::SupportsAutofocus() const { + return false; +} + +bool HTMLFormControlElement::IsAutofocusable() const { + return FastHasAttribute(autofocusAttr) && SupportsAutofocus(); +} + +void HTMLFormControlElement::SetAutofilled(bool autofilled) { + if (autofilled == is_autofilled_) + return; + + is_autofilled_ = autofilled; + PseudoStateChanged(CSSSelector::kPseudoAutofill); +} + +const AtomicString& HTMLFormControlElement::autocapitalize() const { + if (!FastGetAttribute(autocapitalizeAttr).IsEmpty()) + return HTMLElement::autocapitalize(); + + // If the form control itself does not have the autocapitalize attribute set, + // but the form owner is non-null and does have the autocapitalize attribute + // set, we inherit from the form owner. + if (HTMLFormElement* form = Form()) + return form->autocapitalize(); + + return g_empty_atom; +} + +static bool ShouldAutofocusOnAttach(const HTMLFormControlElement* element) { + if (!element->IsAutofocusable()) + return false; + if (element->GetDocument().IsSandboxed(kSandboxAutomaticFeatures)) { + // FIXME: This message should be moved off the console once a solution to + // https://bugs.webkit.org/show_bug.cgi?id=103274 exists. + element->GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Blocked autofocusing on a form control because the form's frame is " + "sandboxed and the 'allow-scripts' permission is not set.")); + return false; + } + + return true; +} + +void HTMLFormControlElement::AttachLayoutTree(AttachContext& context) { + HTMLElement::AttachLayoutTree(context); + + if (!GetLayoutObject()) + return; + + // The call to updateFromElement() needs to go after the call through + // to the base class's attachLayoutTree() because that can sometimes do a + // close on the layoutObject. + GetLayoutObject()->UpdateFromElement(); + + // FIXME: Autofocus handling should be moved to insertedInto according to + // the standard. + if (ShouldAutofocusOnAttach(this)) + GetDocument().SetAutofocusElement(this); +} + +void HTMLFormControlElement::DidMoveToNewDocument(Document& old_document) { + ListedElement::DidMoveToNewDocument(old_document); + HTMLElement::DidMoveToNewDocument(old_document); +} + +Node::InsertionNotificationRequest HTMLFormControlElement::InsertedInto( + ContainerNode* insertion_point) { + ancestor_disabled_state_ = kAncestorDisabledStateUnknown; + // Force traversal to find ancestor + may_have_field_set_ancestor_ = true; + data_list_ancestor_state_ = kUnknown; + SetNeedsWillValidateCheck(); + HTMLElement::InsertedInto(insertion_point); + ListedElement::InsertedInto(insertion_point); + FieldSetAncestorsSetNeedsValidityCheck(insertion_point); + + // Trigger for elements outside of forms. + if (!formOwner() && insertion_point->isConnected()) + GetDocument().DidAssociateFormControl(this); + + return kInsertionDone; +} + +void HTMLFormControlElement::RemovedFrom(ContainerNode* insertion_point) { + FieldSetAncestorsSetNeedsValidityCheck(insertion_point); + HideVisibleValidationMessage(); + has_validation_message_ = false; + ancestor_disabled_state_ = kAncestorDisabledStateUnknown; + data_list_ancestor_state_ = kUnknown; + SetNeedsWillValidateCheck(); + HTMLElement::RemovedFrom(insertion_point); + ListedElement::RemovedFrom(insertion_point); +} + +void HTMLFormControlElement::WillChangeForm() { + ListedElement::WillChangeForm(); + FormOwnerSetNeedsValidityCheck(); + if (formOwner() && CanBeSuccessfulSubmitButton()) + formOwner()->InvalidateDefaultButtonStyle(); +} + +void HTMLFormControlElement::DidChangeForm() { + ListedElement::DidChangeForm(); + FormOwnerSetNeedsValidityCheck(); + if (formOwner() && isConnected() && CanBeSuccessfulSubmitButton()) + formOwner()->InvalidateDefaultButtonStyle(); +} + +void HTMLFormControlElement::FormOwnerSetNeedsValidityCheck() { + if (HTMLFormElement* form = formOwner()) { + form->PseudoStateChanged(CSSSelector::kPseudoValid); + form->PseudoStateChanged(CSSSelector::kPseudoInvalid); + } +} + +void HTMLFormControlElement::FieldSetAncestorsSetNeedsValidityCheck( + Node* node) { + if (!node) + return; + if (!may_have_field_set_ancestor_) + return; + for (HTMLFieldSetElement* field_set = + Traversal<HTMLFieldSetElement>::FirstAncestorOrSelf(*node); + field_set; + field_set = Traversal<HTMLFieldSetElement>::FirstAncestor(*field_set)) { + field_set->PseudoStateChanged(CSSSelector::kPseudoValid); + field_set->PseudoStateChanged(CSSSelector::kPseudoInvalid); + } +} + +void HTMLFormControlElement::DispatchChangeEvent() { + DispatchScopedEvent(Event::CreateBubble(EventTypeNames::change)); +} + +HTMLFormElement* HTMLFormControlElement::formOwner() const { + return ListedElement::Form(); +} + +bool HTMLFormControlElement::IsDisabledFormControl() const { + if (FastHasAttribute(disabledAttr)) + return true; + + // Since the MHTML is loaded in sandboxing mode with form submission and + // script execution disabled, we should gray out all form control elements + // to indicate that the form cannot be worked on. + if (GetDocument().Fetcher()->Archive()) + return true; + + if (ancestor_disabled_state_ == kAncestorDisabledStateUnknown) + UpdateAncestorDisabledState(); + return ancestor_disabled_state_ == kAncestorDisabledStateDisabled; +} + +bool HTMLFormControlElement::MatchesEnabledPseudoClass() const { + return !IsDisabledFormControl(); +} + +bool HTMLFormControlElement::IsRequired() const { + return FastHasAttribute(requiredAttr); +} + +String HTMLFormControlElement::ResultForDialogSubmit() { + return FastGetAttribute(valueAttr); +} + +void HTMLFormControlElement::DidRecalcStyle(StyleRecalcChange) { + if (LayoutObject* layout_object = GetLayoutObject()) + layout_object->UpdateFromElement(); +} + +bool HTMLFormControlElement::SupportsFocus() const { + return !IsDisabledFormControl(); +} + +bool HTMLFormControlElement::IsKeyboardFocusable() const { + // Skip tabIndex check in a parent class. + return IsFocusable(); +} + +bool HTMLFormControlElement::ShouldShowFocusRingOnMouseFocus() const { + return false; +} + +bool HTMLFormControlElement::ShouldHaveFocusAppearance() const { + return !WasFocusedByMouse() || ShouldShowFocusRingOnMouseFocus(); +} + +void HTMLFormControlElement::WillCallDefaultEventHandler(const Event& event) { + if (!WasFocusedByMouse()) + return; + if (!event.IsKeyboardEvent() || event.type() != EventTypeNames::keydown) + return; + + bool old_should_have_focus_appearance = ShouldHaveFocusAppearance(); + SetWasFocusedByMouse(false); + + // Changes to WasFocusedByMouse may affect ShouldHaveFocusAppearance() and + // LayoutTheme::IsFocused(). Inform LayoutTheme if + // ShouldHaveFocusAppearance() changes. + if (old_should_have_focus_appearance != ShouldHaveFocusAppearance() && + GetLayoutObject()) { + GetLayoutObject()->InvalidateIfControlStateChanged(kFocusControlState); + } +} + +int HTMLFormControlElement::tabIndex() const { + // Skip the supportsFocus check in HTMLElement. + return Element::tabIndex(); +} + +bool HTMLFormControlElement::RecalcWillValidate() const { + if (data_list_ancestor_state_ == kUnknown) { + if (Traversal<HTMLDataListElement>::FirstAncestor(*this)) + data_list_ancestor_state_ = kInsideDataList; + else + data_list_ancestor_state_ = kNotInsideDataList; + } + return data_list_ancestor_state_ == kNotInsideDataList && + !IsDisabledOrReadOnly(); +} + +bool HTMLFormControlElement::willValidate() const { + if (!will_validate_initialized_ || data_list_ancestor_state_ == kUnknown) { + const_cast<HTMLFormControlElement*>(this)->SetNeedsWillValidateCheck(); + } else { + // If the following assertion fails, setNeedsWillValidateCheck() is not + // called correctly when something which changes recalcWillValidate() result + // is updated. + DCHECK_EQ(will_validate_, RecalcWillValidate()); + } + return will_validate_; +} + +void HTMLFormControlElement::SetNeedsWillValidateCheck() { + // We need to recalculate willValidate immediately because willValidate change + // can causes style change. + bool new_will_validate = RecalcWillValidate(); + if (will_validate_initialized_ && will_validate_ == new_will_validate) + return; + will_validate_initialized_ = true; + will_validate_ = new_will_validate; + // Needs to force setNeedsValidityCheck() to invalidate validity state of + // FORM/FIELDSET. If this element updates willValidate twice and + // isValidElement() is not called between them, the second call of this + // function still has m_validityIsDirty==true, which means + // setNeedsValidityCheck() doesn't invalidate validity state of + // FORM/FIELDSET. + validity_is_dirty_ = false; + SetNeedsValidityCheck(); + // No need to trigger style recalculation here because + // setNeedsValidityCheck() does it in the right away. This relies on + // the assumption that valid() is always true if willValidate() is false. + + if (!will_validate_) + HideVisibleValidationMessage(); +} + +void HTMLFormControlElement::FindCustomValidationMessageTextDirection( + const String& message, + TextDirection& message_dir, + String& sub_message, + TextDirection& sub_message_dir) { + message_dir = DetermineDirectionality(message); + if (!sub_message.IsEmpty()) + sub_message_dir = GetLayoutObject()->Style()->Direction(); +} + +void HTMLFormControlElement::UpdateVisibleValidationMessage() { + Page* page = GetDocument().GetPage(); + if (!page || !page->IsPageVisible() || GetDocument().UnloadStarted()) + return; + if (page->Paused()) + return; + String message; + if (GetLayoutObject() && willValidate()) + message = validationMessage().StripWhiteSpace(); + + has_validation_message_ = true; + ValidationMessageClient* client = &page->GetValidationMessageClient(); + TextDirection message_dir = TextDirection::kLtr; + TextDirection sub_message_dir = TextDirection::kLtr; + String sub_message = ValidationSubMessage().StripWhiteSpace(); + if (message.IsEmpty()) { + client->HideValidationMessage(*this); + } else { + FindCustomValidationMessageTextDirection(message, message_dir, sub_message, + sub_message_dir); + } + client->ShowValidationMessage(*this, message, message_dir, sub_message, + sub_message_dir); +} + +void HTMLFormControlElement::HideVisibleValidationMessage() { + if (!has_validation_message_) + return; + + if (ValidationMessageClient* client = GetValidationMessageClient()) + client->HideValidationMessage(*this); +} + +bool HTMLFormControlElement::IsValidationMessageVisible() const { + if (!has_validation_message_) + return false; + + ValidationMessageClient* client = GetValidationMessageClient(); + if (!client) + return false; + + return client->IsValidationMessageVisible(*this); +} + +ValidationMessageClient* HTMLFormControlElement::GetValidationMessageClient() + const { + Page* page = GetDocument().GetPage(); + if (!page) + return nullptr; + + return &page->GetValidationMessageClient(); +} + +bool HTMLFormControlElement::checkValidity( + HeapVector<Member<HTMLFormControlElement>>* unhandled_invalid_controls, + CheckValidityEventBehavior event_behavior) { + if (!willValidate()) + return true; + if (IsValidElement()) + return true; + if (event_behavior != kCheckValidityDispatchInvalidEvent) + return false; + Document* original_document = &GetDocument(); + DispatchEventResult dispatch_result = + DispatchEvent(Event::CreateCancelable(EventTypeNames::invalid)); + if (dispatch_result == DispatchEventResult::kNotCanceled && + unhandled_invalid_controls && isConnected() && + original_document == GetDocument()) + unhandled_invalid_controls->push_back(this); + return false; +} + +void HTMLFormControlElement::ShowValidationMessage() { + scrollIntoViewIfNeeded(false); + focus(); + UpdateVisibleValidationMessage(); +} + +bool HTMLFormControlElement::reportValidity() { + HeapVector<Member<HTMLFormControlElement>> unhandled_invalid_controls; + bool is_valid = checkValidity(&unhandled_invalid_controls, + kCheckValidityDispatchInvalidEvent); + if (is_valid || unhandled_invalid_controls.IsEmpty()) + return is_valid; + DCHECK_EQ(unhandled_invalid_controls.size(), 1u); + DCHECK_EQ(unhandled_invalid_controls[0].Get(), this); + // Update layout now before calling isFocusable(), which has + // !layoutObject()->needsLayout() assertion. + GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + if (IsFocusable()) { + ShowValidationMessage(); + return false; + } + if (GetDocument().GetFrame()) { + String message( + "An invalid form control with name='%name' is not focusable."); + message.Replace("%name", GetName()); + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kRenderingMessageSource, kErrorMessageLevel, message)); + } + return false; +} + +bool HTMLFormControlElement::MatchesValidityPseudoClasses() const { + return willValidate(); +} + +bool HTMLFormControlElement::IsValidElement() { + if (validity_is_dirty_) { + is_valid_ = !willValidate() || Valid(); + validity_is_dirty_ = false; + } else { + // If the following assertion fails, setNeedsValidityCheck() is not + // called correctly when something which changes validity is updated. + DCHECK_EQ(is_valid_, (!willValidate() || Valid())); + } + return is_valid_; +} + +void HTMLFormControlElement::SetNeedsValidityCheck() { + if (!validity_is_dirty_) { + validity_is_dirty_ = true; + FormOwnerSetNeedsValidityCheck(); + FieldSetAncestorsSetNeedsValidityCheck(parentNode()); + PseudoStateChanged(CSSSelector::kPseudoValid); + PseudoStateChanged(CSSSelector::kPseudoInvalid); + } + + // Updates only if this control already has a validation message. + if (IsValidationMessageVisible()) { + // Calls UpdateVisibleValidationMessage() even if is_valid_ is not + // changed because a validation message can be changed. + GetDocument() + .GetTaskRunner(TaskType::kDOMManipulation) + ->PostTask( + FROM_HERE, + WTF::Bind(&HTMLFormControlElement::UpdateVisibleValidationMessage, + WrapPersistent(this))); + } +} + +void HTMLFormControlElement::setCustomValidity(const String& error) { + ListedElement::setCustomValidity(error); + SetNeedsValidityCheck(); +} + +void HTMLFormControlElement::DispatchBlurEvent( + Element* new_focused_element, + WebFocusType type, + InputDeviceCapabilities* source_capabilities) { + HTMLElement::DispatchBlurEvent(new_focused_element, type, + source_capabilities); + HideVisibleValidationMessage(); +} + +bool HTMLFormControlElement::IsSuccessfulSubmitButton() const { + return CanBeSuccessfulSubmitButton() && !IsDisabledFormControl(); +} + +// static +const HTMLFormControlElement* +HTMLFormControlElement::EnclosingFormControlElement(const Node* node) { + if (!node) + return nullptr; + return Traversal<HTMLFormControlElement>::FirstAncestorOrSelf(*node); +} + +String HTMLFormControlElement::NameForAutofill() const { + String full_name = GetName(); + String trimmed_name = full_name.StripWhiteSpace(); + if (!trimmed_name.IsEmpty()) + return trimmed_name; + full_name = GetIdAttribute(); + trimmed_name = full_name.StripWhiteSpace(); + return trimmed_name; +} + +void HTMLFormControlElement::CloneNonAttributePropertiesFrom( + const Element& source, + CloneChildrenFlag flag) { + HTMLElement::CloneNonAttributePropertiesFrom(source, flag); + SetNeedsValidityCheck(); +} + +void HTMLFormControlElement::AssociateWith(HTMLFormElement* form) { + AssociateByParser(form); +}; + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h new file mode 100644 index 00000000000..e85ee8d44d0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element.h @@ -0,0 +1,231 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/form_associated.h" +#include "third_party/blink/renderer/core/html/forms/labelable_element.h" +#include "third_party/blink/renderer/core/html/forms/listed_element.h" + +namespace blink { + +class HTMLFormElement; +class ValidationMessageClient; + +enum CheckValidityEventBehavior { + kCheckValidityDispatchNoEvent, + kCheckValidityDispatchInvalidEvent +}; + +// HTMLFormControlElement is the default implementation of +// ListedElement, and listed element implementations should use +// HTMLFormControlElement unless there is a special reason. +class CORE_EXPORT HTMLFormControlElement : public LabelableElement, + public ListedElement, + public FormAssociated { + USING_GARBAGE_COLLECTED_MIXIN(HTMLFormControlElement); + + public: + ~HTMLFormControlElement() override; + void Trace(blink::Visitor*) override; + + String formAction() const; + void setFormAction(const AtomicString&); + String formEnctype() const; + void setFormEnctype(const AtomicString&); + String formMethod() const; + void setFormMethod(const AtomicString&); + bool FormNoValidate() const; + + void AncestorDisabledStateWasChanged(); + + void Reset(); + + void DispatchChangeEvent(); + + HTMLFormElement* formOwner() const final; + + bool IsDisabledFormControl() const override; + + bool MatchesEnabledPseudoClass() const override; + + bool IsEnumeratable() const override { return false; } + + bool IsRequired() const; + + const AtomicString& type() const { return FormControlType(); } + + virtual const AtomicString& FormControlType() const = 0; + + virtual bool CanTriggerImplicitSubmission() const { return false; } + + virtual bool IsSubmittableElement() { return true; } + + virtual String ResultForDialogSubmit(); + + // Return true if this control type can be a submit button. This doesn't + // check |disabled|, and this doesn't check if this is the first submit + // button. + virtual bool CanBeSuccessfulSubmitButton() const { return false; } + // Return true if this control can submit a form. + // i.e. canBeSuccessfulSubmitButton() && !isDisabledFormControl(). + bool IsSuccessfulSubmitButton() const; + virtual bool IsActivatedSubmit() const { return false; } + virtual void SetActivatedSubmit(bool) {} + + bool willValidate() const override; + + void UpdateVisibleValidationMessage(); + void HideVisibleValidationMessage(); + bool checkValidity( + HeapVector<Member<HTMLFormControlElement>>* unhandled_invalid_controls = + nullptr, + CheckValidityEventBehavior = kCheckValidityDispatchInvalidEvent); + bool reportValidity(); + // This must be called only after the caller check the element is focusable. + void ShowValidationMessage(); + bool IsValidationMessageVisible() const; + // This must be called when a validation constraint or control value is + // changed. + void SetNeedsValidityCheck(); + void setCustomValidity(const String&) final; + void FindCustomValidationMessageTextDirection(const String& message, + TextDirection& message_dir, + String& sub_message, + TextDirection& sub_message_dir); + + bool IsReadOnly() const; + bool IsDisabledOrReadOnly() const; + + bool IsAutofocusable() const; + + virtual bool ShouldShowFocusRingOnMouseFocus() const; + + bool IsAutofilled() const { return is_autofilled_; } + void SetAutofilled(bool = true); + + const AtomicString& autocapitalize() const final; + + static const HTMLFormControlElement* EnclosingFormControlElement(const Node*); + + String NameForAutofill() const; + + void CloneNonAttributePropertiesFrom(const Element&, + CloneChildrenFlag) override; + + FormAssociated* ToFormAssociatedOrNull() override { return this; }; + void AssociateWith(HTMLFormElement*) override; + + bool BlocksFormSubmission() const { return blocks_form_submission_; } + void SetBlocksFormSubmission(bool value) { blocks_form_submission_ = value; } + + protected: + HTMLFormControlElement(const QualifiedName& tag_name, Document&); + + void AttributeChanged(const AttributeModificationParams&) override; + void ParseAttribute(const AttributeModificationParams&) override; + virtual void RequiredAttributeChanged(); + virtual void DisabledAttributeChanged(); + void AttachLayoutTree(AttachContext&) override; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void RemovedFrom(ContainerNode*) override; + void WillChangeForm() override; + void DidChangeForm() override; + void DidMoveToNewDocument(Document& old_document) override; + + bool SupportsFocus() const override; + bool IsKeyboardFocusable() const override; + bool ShouldHaveFocusAppearance() const final; + void DispatchBlurEvent(Element* new_focused_element, + WebFocusType, + InputDeviceCapabilities* source_capabilities) override; + void WillCallDefaultEventHandler(const Event&) final; + + void DidRecalcStyle(StyleRecalcChange) override; + + // This must be called any time the result of willValidate() has changed. + void SetNeedsWillValidateCheck(); + virtual bool RecalcWillValidate() const; + + virtual void ResetImpl() {} + virtual bool SupportsAutofocus() const; + + private: + bool IsFormControlElement() const final { return true; } + bool AlwaysCreateUserAgentShadowRoot() const override { return true; } + + int tabIndex() const override; + + bool IsValidElement() override; + bool MatchesValidityPseudoClasses() const override; + void UpdateAncestorDisabledState() const; + + ValidationMessageClient* GetValidationMessageClient() const; + + // Requests validity recalc for the form owner, if one exists. + void FormOwnerSetNeedsValidityCheck(); + // Requests validity recalc for all ancestor fieldsets, if exist. + void FieldSetAncestorsSetNeedsValidityCheck(Node*); + + enum AncestorDisabledState { + kAncestorDisabledStateUnknown, + kAncestorDisabledStateEnabled, + kAncestorDisabledStateDisabled + }; + mutable AncestorDisabledState ancestor_disabled_state_; + enum DataListAncestorState { kUnknown, kInsideDataList, kNotInsideDataList }; + mutable enum DataListAncestorState data_list_ancestor_state_; + mutable bool may_have_field_set_ancestor_ : 1; + + bool is_autofilled_ : 1; + bool has_validation_message_ : 1; + // The initial value of m_willValidate depends on the derived class. We can't + // initialize it with a virtual function in the constructor. m_willValidate + // is not deterministic as long as m_willValidateInitialized is false. + mutable bool will_validate_initialized_ : 1; + mutable bool will_validate_ : 1; + + // Cache of valid(). + bool is_valid_ : 1; + bool validity_is_dirty_ : 1; + + bool blocks_form_submission_ : 1; +}; + +inline bool IsHTMLFormControlElement(const Element& element) { + return element.IsFormControlElement(); +} + +DEFINE_HTMLELEMENT_TYPE_CASTS_WITH_FUNCTION(HTMLFormControlElement); +DEFINE_TYPE_CASTS(HTMLFormControlElement, + ListedElement, + control, + control->IsFormControlElement(), + control.IsFormControlElement()); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc new file mode 100644 index 00000000000..96133c5afce --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_test.cc @@ -0,0 +1,153 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/page/scoped_page_pauser.h" +#include "third_party/blink/renderer/core/page/validation_message_client.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" + +namespace blink { + +namespace { +class MockFormValidationMessageClient + : public GarbageCollectedFinalized<MockFormValidationMessageClient>, + public ValidationMessageClient { + USING_GARBAGE_COLLECTED_MIXIN(MockFormValidationMessageClient); + + public: + void ShowValidationMessage(const Element& anchor, + const String&, + TextDirection, + const String&, + TextDirection) override { + anchor_ = anchor; + ++operation_count_; + } + + void HideValidationMessage(const Element& anchor) override { + if (anchor_ == &anchor) + anchor_ = nullptr; + ++operation_count_; + } + + bool IsValidationMessageVisible(const Element& anchor) override { + return anchor_ == &anchor; + } + + void DocumentDetached(const Document&) override {} + void WillBeDestroyed() override {} + void Trace(blink::Visitor* visitor) override { + visitor->Trace(anchor_); + ValidationMessageClient::Trace(visitor); + } + + // The number of calls of ShowValidationMessage() and HideValidationMessage(). + int OperationCount() const { return operation_count_; } + + private: + Member<const Element> anchor_; + int operation_count_ = 0; +}; +} // namespace + +class HTMLFormControlElementTest : public PageTestBase { + protected: + void SetUp() override; +}; + +void HTMLFormControlElementTest::SetUp() { + Page::PageClients page_clients; + FillWithEmptyClients(page_clients); + SetupPageWithClients(&page_clients); + GetDocument().SetMimeType("text/html"); +} + +TEST_F(HTMLFormControlElementTest, customValidationMessageTextDirection) { + SetHtmlInnerHTML("<body><input pattern='abc' value='def' id=input></body>"); + + HTMLInputElement* input = ToHTMLInputElement(GetElementById("input")); + input->setCustomValidity( + String::FromUTF8("\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x89")); + input->setAttribute( + HTMLNames::titleAttr, + AtomicString::FromUTF8("\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x89")); + + String message = input->validationMessage().StripWhiteSpace(); + String sub_message = input->ValidationSubMessage().StripWhiteSpace(); + TextDirection message_dir = TextDirection::kRtl; + TextDirection sub_message_dir = TextDirection::kLtr; + + input->FindCustomValidationMessageTextDirection(message, message_dir, + sub_message, sub_message_dir); + EXPECT_EQ(TextDirection::kRtl, message_dir); + EXPECT_EQ(TextDirection::kLtr, sub_message_dir); + + scoped_refptr<ComputedStyle> rtl_style = + ComputedStyle::Clone(input->GetLayoutObject()->StyleRef()); + rtl_style->SetDirection(TextDirection::kRtl); + input->GetLayoutObject()->SetStyle(std::move(rtl_style)); + input->FindCustomValidationMessageTextDirection(message, message_dir, + sub_message, sub_message_dir); + EXPECT_EQ(TextDirection::kRtl, message_dir); + EXPECT_EQ(TextDirection::kLtr, sub_message_dir); + + input->setCustomValidity(String::FromUTF8("Main message.")); + message = input->validationMessage().StripWhiteSpace(); + sub_message = input->ValidationSubMessage().StripWhiteSpace(); + input->FindCustomValidationMessageTextDirection(message, message_dir, + sub_message, sub_message_dir); + EXPECT_EQ(TextDirection::kLtr, message_dir); + EXPECT_EQ(TextDirection::kLtr, sub_message_dir); + + input->setCustomValidity(String()); + message = input->validationMessage().StripWhiteSpace(); + sub_message = input->ValidationSubMessage().StripWhiteSpace(); + input->FindCustomValidationMessageTextDirection(message, message_dir, + sub_message, sub_message_dir); + EXPECT_EQ(TextDirection::kLtr, message_dir); + EXPECT_EQ(TextDirection::kRtl, sub_message_dir); +} + +TEST_F(HTMLFormControlElementTest, UpdateValidationMessageSkippedIfPrinting) { + SetHtmlInnerHTML("<body><input required id=input></body>"); + ValidationMessageClient* validation_message_client = + new MockFormValidationMessageClient(); + GetPage().SetValidationMessageClient(validation_message_client); + Page::OrdinaryPages().insert(&GetPage()); + + HTMLInputElement* input = ToHTMLInputElement(GetElementById("input")); + ScopedPagePauser pauser; // print() pauses the page. + input->reportValidity(); + EXPECT_FALSE(validation_message_client->IsValidationMessageVisible(*input)); +} + +TEST_F(HTMLFormControlElementTest, DoNotUpdateLayoutDuringDOMMutation) { + // The real ValidationMessageClient has UpdateStyleAndLayout*() in + // ShowValidationMessage(). So calling it during DOM mutation is + // dangerous. This test ensures ShowValidationMessage() is NOT called in + // appendChild(). crbug.com/756408 + GetDocument().documentElement()->SetInnerHTMLFromString("<select></select>"); + HTMLFormControlElement* const select = + ToHTMLFormControlElement(GetDocument().QuerySelector("select")); + auto* const optgroup = GetDocument().CreateRawElement(HTMLNames::optgroupTag); + auto* validation_client = new MockFormValidationMessageClient(); + GetDocument().GetPage()->SetValidationMessageClient(validation_client); + + select->setCustomValidity("foobar"); + select->reportValidity(); + int start_operation_count = validation_client->OperationCount(); + select->appendChild(optgroup); + EXPECT_EQ(start_operation_count, validation_client->OperationCount()) + << "DOM mutation should not handle validation message UI in it."; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc new file mode 100644 index 00000000000..430f7bc185b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc @@ -0,0 +1,96 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" + +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" + +namespace blink { + +HTMLFormControlElementWithState::HTMLFormControlElementWithState( + const QualifiedName& tag_name, + Document& doc) + : HTMLFormControlElement(tag_name, doc) {} + +HTMLFormControlElementWithState::~HTMLFormControlElementWithState() = default; + +Node::InsertionNotificationRequest +HTMLFormControlElementWithState::InsertedInto(ContainerNode* insertion_point) { + if (insertion_point->isConnected() && !ContainingShadowRoot()) + GetDocument().GetFormController().RegisterStatefulFormControl(*this); + return HTMLFormControlElement::InsertedInto(insertion_point); +} + +void HTMLFormControlElementWithState::RemovedFrom( + ContainerNode* insertion_point) { + if (insertion_point->isConnected() && !ContainingShadowRoot() && + !insertion_point->ContainingShadowRoot()) + GetDocument().GetFormController().UnregisterStatefulFormControl(*this); + HTMLFormControlElement::RemovedFrom(insertion_point); +} + +bool HTMLFormControlElementWithState::ShouldAutocomplete() const { + if (!Form()) + return true; + return Form()->ShouldAutocomplete(); +} + +void HTMLFormControlElementWithState::NotifyFormStateChanged() { + // This can be called during fragment parsing as a result of option + // selection before the document is active (or even in a frame). + if (!GetDocument().IsActive()) + return; + GetDocument().GetFrame()->Client()->DidUpdateCurrentHistoryItem(); +} + +bool HTMLFormControlElementWithState::ShouldSaveAndRestoreFormControlState() + const { + // We don't save/restore control state in a form with autocomplete=off. + return isConnected() && ShouldAutocomplete(); +} + +FormControlState HTMLFormControlElementWithState::SaveFormControlState() const { + return FormControlState(); +} + +void HTMLFormControlElementWithState::FinishParsingChildren() { + HTMLFormControlElement::FinishParsingChildren(); + GetDocument().GetFormController().RestoreControlStateFor(*this); +} + +bool HTMLFormControlElementWithState::IsFormControlElementWithState() const { + return true; +} + +void HTMLFormControlElementWithState::Trace(Visitor* visitor) { + visitor->Trace(prev_); + visitor->Trace(next_); + HTMLFormControlElement::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h new file mode 100644 index 00000000000..fab6339d451 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_WITH_STATE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROL_ELEMENT_WITH_STATE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/platform/wtf/doubly_linked_list.h" + +namespace blink { + +class FormControlState; + +class HTMLFormControlElementWithState; +// Cannot use eager tracing as HTMLFormControlElementWithState objects are part +// of a HeapDoublyLinkedList and have pointers to the previous and next elements +// in the list, which can cause very deep stacks in eager tracing. +WILL_NOT_BE_EAGERLY_TRACED_CLASS(HTMLFormControlElementWithState); + +class CORE_EXPORT HTMLFormControlElementWithState + : public HTMLFormControlElement, + public DoublyLinkedListNode<HTMLFormControlElementWithState> { + public: + ~HTMLFormControlElementWithState() override; + + bool CanContainRangeEndPoint() const final { return false; } + + virtual bool ShouldAutocomplete() const; + virtual bool ShouldSaveAndRestoreFormControlState() const; + virtual FormControlState SaveFormControlState() const; + // The specified FormControlState must have at least one string value. + virtual void RestoreFormControlState(const FormControlState&) {} + void NotifyFormStateChanged(); + + void Trace(Visitor*) override; + + protected: + HTMLFormControlElementWithState(const QualifiedName& tag_name, Document&); + + void FinishParsingChildren() override; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void RemovedFrom(ContainerNode*) override; + bool IsFormControlElementWithState() const final; + + private: + bool ShouldForceLegacyLayout() const final { return true; } + + // Pointers for DoublyLinkedListNode<HTMLFormControlElementWithState>. This + // is used for adding an instance to a list of form controls stored in + // DocumentState. Each instance is only added to its containing document's + // DocumentState list. + friend class WTF::DoublyLinkedListNode<HTMLFormControlElementWithState>; + Member<HTMLFormControlElementWithState> prev_; + Member<HTMLFormControlElementWithState> next_; +}; + +DEFINE_TYPE_CASTS(HTMLFormControlElementWithState, + ListedElement, + control, + control->IsFormControlElementWithState(), + control.IsFormControlElementWithState()); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc new file mode 100644 index 00000000000..8f0d4784c76 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.cc @@ -0,0 +1,229 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010, 2011, 2012 Apple Inc. All + * rights reserved. + * Copyright (C) 2014 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 "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h" + +#include "third_party/blink/renderer/bindings/core/v8/radio_node_list_or_element.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/html_image_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" + +namespace blink { + +using namespace HTMLNames; + +// Since the collections are to be "live", we have to do the +// calculation every time if anything has changed. + +HTMLFormControlsCollection::HTMLFormControlsCollection( + ContainerNode& owner_node) + : HTMLCollection(owner_node, kFormControls, kOverridesItemAfter), + cached_element_(nullptr), + cached_element_offset_in_array_(0) { + DCHECK(IsHTMLFormElement(owner_node)); +} + +HTMLFormControlsCollection* HTMLFormControlsCollection::Create( + ContainerNode& owner_node, + CollectionType type) { + DCHECK_EQ(type, kFormControls); + return new HTMLFormControlsCollection(owner_node); +} + +HTMLFormControlsCollection::~HTMLFormControlsCollection() = default; + +const ListedElement::List& HTMLFormControlsCollection::ListedElements() const { + return ToHTMLFormElement(ownerNode()).ListedElements(); +} + +const HeapVector<Member<HTMLImageElement>>& +HTMLFormControlsCollection::FormImageElements() const { + return ToHTMLFormElement(ownerNode()).ImageElements(); +} + +static unsigned FindListedElement(const ListedElement::List& listed_elements, + Element* element) { + unsigned i = 0; + for (; i < listed_elements.size(); ++i) { + ListedElement* listed_element = listed_elements[i]; + if (listed_element->IsEnumeratable() && + ToHTMLElement(listed_element) == element) + break; + } + return i; +} + +HTMLElement* HTMLFormControlsCollection::VirtualItemAfter( + Element* previous) const { + const ListedElement::List& listed_elements = ListedElements(); + unsigned offset; + if (!previous) + offset = 0; + else if (cached_element_ == previous) + offset = cached_element_offset_in_array_ + 1; + else + offset = FindListedElement(listed_elements, previous) + 1; + + for (unsigned i = offset; i < listed_elements.size(); ++i) { + ListedElement* listed_element = listed_elements[i]; + if (listed_element->IsEnumeratable()) { + cached_element_ = ToHTMLElement(listed_element); + cached_element_offset_in_array_ = i; + return cached_element_; + } + } + return nullptr; +} + +void HTMLFormControlsCollection::InvalidateCache(Document* old_document) const { + HTMLCollection::InvalidateCache(old_document); + cached_element_ = nullptr; + cached_element_offset_in_array_ = 0; +} + +static HTMLElement* FirstNamedItem(const ListedElement::List& elements_array, + const QualifiedName& attr_name, + const String& name) { + DCHECK(attr_name == idAttr || attr_name == nameAttr); + + for (const auto& listed_element : elements_array) { + HTMLElement* element = ToHTMLElement(listed_element); + if (listed_element->IsEnumeratable() && + element->FastGetAttribute(attr_name) == name) + return element; + } + return nullptr; +} + +HTMLElement* HTMLFormControlsCollection::namedItem( + const AtomicString& name) const { + // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp + // This method first searches for an object with a matching id + // attribute. If a match is not found, the method then searches for an + // object with a matching name attribute, but only on those elements + // that are allowed a name attribute. + if (HTMLElement* item = FirstNamedItem(ListedElements(), idAttr, name)) + return item; + return FirstNamedItem(ListedElements(), nameAttr, name); +} + +void HTMLFormControlsCollection::UpdateIdNameCache() const { + if (HasValidIdNameCache()) + return; + + NamedItemCache* cache = NamedItemCache::Create(); + HashSet<StringImpl*> found_input_elements; + + for (const auto& listed_element : ListedElements()) { + if (listed_element->IsEnumeratable()) { + HTMLElement* element = ToHTMLElement(listed_element); + const AtomicString& id_attr_val = element->GetIdAttribute(); + const AtomicString& name_attr_val = element->GetNameAttribute(); + if (!id_attr_val.IsEmpty()) { + cache->AddElementWithId(id_attr_val, element); + found_input_elements.insert(id_attr_val.Impl()); + } + if (!name_attr_val.IsEmpty() && id_attr_val != name_attr_val) { + cache->AddElementWithName(name_attr_val, element); + found_input_elements.insert(name_attr_val.Impl()); + } + } + } + + // HTMLFormControlsCollection doesn't support named getter for IMG + // elements. However we still need to handle IMG elements here because + // HTMLFormElement named getter relies on this. + for (const auto& element : FormImageElements()) { + const AtomicString& id_attr_val = element->GetIdAttribute(); + const AtomicString& name_attr_val = element->GetNameAttribute(); + if (!id_attr_val.IsEmpty() && + !found_input_elements.Contains(id_attr_val.Impl())) + cache->AddElementWithId(id_attr_val, element); + if (!name_attr_val.IsEmpty() && id_attr_val != name_attr_val && + !found_input_elements.Contains(name_attr_val.Impl())) + cache->AddElementWithName(name_attr_val, element); + } + + // Set the named item cache last as traversing the tree may cause cache + // invalidation. + SetNamedItemCache(cache); +} + +void HTMLFormControlsCollection::namedGetter( + const AtomicString& name, + RadioNodeListOrElement& return_value) { + HeapVector<Member<Element>> named_items; + NamedItems(name, named_items); + + if (named_items.IsEmpty()) + return; + + if (named_items.size() == 1) { + if (!IsHTMLImageElement(*named_items[0])) + return_value.SetElement(named_items.at(0)); + return; + } + + // This path never returns a RadioNodeList for <img> because + // onlyMatchingImgElements flag is false by default. + return_value.SetRadioNodeList(ownerNode().GetRadioNodeList(name)); +} + +void HTMLFormControlsCollection::SupportedPropertyNames(Vector<String>& names) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmlformcontrolscollection-0: + // The supported property names consist of the non-empty values of all the id + // and name attributes of all the elements represented by the collection, in + // tree order, ignoring later duplicates, with the id of an element preceding + // its name if it contributes both, they differ from each other, and neither + // is the duplicate of an earlier entry. + HashSet<AtomicString> existing_names; + unsigned length = this->length(); + for (unsigned i = 0; i < length; ++i) { + HTMLElement* element = item(i); + DCHECK(element); + const AtomicString& id_attribute = element->GetIdAttribute(); + if (!id_attribute.IsEmpty()) { + HashSet<AtomicString>::AddResult add_result = + existing_names.insert(id_attribute); + if (add_result.is_new_entry) + names.push_back(id_attribute); + } + const AtomicString& name_attribute = element->GetNameAttribute(); + if (!name_attribute.IsEmpty()) { + HashSet<AtomicString>::AddResult add_result = + existing_names.insert(name_attribute); + if (add_result.is_new_entry) + names.push_back(name_attribute); + } + } +} + +void HTMLFormControlsCollection::Trace(blink::Visitor* visitor) { + visitor->Trace(cached_element_); + HTMLCollection::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h new file mode 100644 index 00000000000..7f9c999cca6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights + * reserved. + * Copyright (C) 2014 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROLS_COLLECTION_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROLS_COLLECTION_H_ + +#include "third_party/blink/renderer/core/html/forms/listed_element.h" +#include "third_party/blink/renderer/core/html/forms/radio_node_list.h" +#include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class HTMLImageElement; +class RadioNodeListOrElement; + +// This class is just a big hack to find form elements even in malformed HTML +// elements. The famous <table><tr><form><td> problem. + +class HTMLFormControlsCollection final : public HTMLCollection { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLFormControlsCollection* Create(ContainerNode&, CollectionType); + + ~HTMLFormControlsCollection() override; + + HTMLElement* item(unsigned offset) const { + return ToHTMLElement(HTMLCollection::item(offset)); + } + + HTMLElement* namedItem(const AtomicString& name) const override; + void namedGetter(const AtomicString& name, RadioNodeListOrElement&); + + void Trace(blink::Visitor*) override; + + private: + explicit HTMLFormControlsCollection(ContainerNode&); + + void UpdateIdNameCache() const override; + void SupportedPropertyNames(Vector<String>& names) override; + + const ListedElement::List& ListedElements() const; + const HeapVector<Member<HTMLImageElement>>& FormImageElements() const; + HTMLElement* VirtualItemAfter(Element*) const override; + void InvalidateCache(Document* old_document = nullptr) const override; + + mutable Member<HTMLElement> cached_element_; + mutable unsigned cached_element_offset_in_array_; +}; +DEFINE_TYPE_CASTS(HTMLFormControlsCollection, + LiveNodeListBase, + collection, + collection->GetType() == kFormControls, + collection.GetType() == kFormControls); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_CONTROLS_COLLECTION_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl new file mode 100644 index 00000000000..674ee55225b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_controls_collection.idl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2006, 2007, 2012 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2014 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. + */ + +// https://html.spec.whatwg.org/#the-htmlformcontrolscollection-interface + +interface HTMLFormControlsCollection : HTMLCollection { + // inherits length and item() + [ImplementedAs=namedGetter] getter (RadioNodeList or Element)? namedItem(DOMString name); // shadows inherited namedItem() + // FIXME: This getter is not in the spec. + [ImplementedAs=item] getter Node (unsigned long index); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc new file mode 100644 index 00000000000..699de8293eb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.cc @@ -0,0 +1,899 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights + * reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_form_element.h" + +#include <limits> +#include "third_party/blink/public/platform/web_insecure_request_policy.h" +#include "third_party/blink/renderer/bindings/core/v8/radio_node_list_or_element.h" +#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" +#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h" +#include "third_party/blink/renderer/core/dom/attribute.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/node_lists_node_data.h" +#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h" +#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_client.h" +#include "third_party/blink/renderer/core/frame/remote_frame.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/form_data_event.h" +#include "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/radio_node_list.h" +#include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/core/html/html_dialog_element.h" +#include "third_party/blink/renderer/core/html/html_image_element.h" +#include "third_party/blink/renderer/core/html/html_object_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/core/loader/form_submission.h" +#include "third_party/blink/renderer/core/loader/mixed_content_checker.h" +#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" +#include "third_party/blink/renderer/platform/wtf/auto_reset.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +using namespace HTMLNames; + +HTMLFormElement::HTMLFormElement(Document& document) + : HTMLElement(formTag, document), + listed_elements_are_dirty_(false), + image_elements_are_dirty_(false), + has_elements_associated_by_parser_(false), + has_elements_associated_by_form_attribute_(false), + did_finish_parsing_children_(false), + is_in_reset_function_(false), + was_demoted_(false) {} + +HTMLFormElement* HTMLFormElement::Create(Document& document) { + UseCounter::Count(document, WebFeature::kFormElement); + return new HTMLFormElement(document); +} + +HTMLFormElement::~HTMLFormElement() = default; + +void HTMLFormElement::Trace(blink::Visitor* visitor) { + visitor->Trace(past_names_map_); + visitor->Trace(radio_button_group_scope_); + visitor->Trace(listed_elements_); + visitor->Trace(image_elements_); + visitor->Trace(planned_navigation_); + HTMLElement::Trace(visitor); +} + +bool HTMLFormElement::MatchesValidityPseudoClasses() const { + return true; +} + +bool HTMLFormElement::IsValidElement() { + return !CheckInvalidControlsAndCollectUnhandled( + nullptr, kCheckValidityDispatchNoEvent); +} + +bool HTMLFormElement::LayoutObjectIsNeeded(const ComputedStyle& style) const { + if (!was_demoted_) + return HTMLElement::LayoutObjectIsNeeded(style); + + ContainerNode* node = parentNode(); + if (!node || !node->GetLayoutObject()) + return HTMLElement::LayoutObjectIsNeeded(style); + LayoutObject* parent_layout_object = node->GetLayoutObject(); + // FIXME: Shouldn't we also check for table caption (see |formIsTablePart| + // below). + // FIXME: This check is not correct for Shadow DOM. + bool parent_is_table_element_part = + (parent_layout_object->IsTable() && IsHTMLTableElement(*node)) || + (parent_layout_object->IsTableRow() && IsHTMLTableRowElement(*node)) || + (parent_layout_object->IsTableSection() && node->HasTagName(tbodyTag)) || + (parent_layout_object->IsLayoutTableCol() && node->HasTagName(colTag)) || + (parent_layout_object->IsTableCell() && IsHTMLTableRowElement(*node)); + + if (!parent_is_table_element_part) + return true; + + EDisplay display = style.Display(); + bool form_is_table_part = + display == EDisplay::kTable || display == EDisplay::kInlineTable || + display == EDisplay::kTableRowGroup || + display == EDisplay::kTableHeaderGroup || + display == EDisplay::kTableFooterGroup || + display == EDisplay::kTableRow || + display == EDisplay::kTableColumnGroup || + display == EDisplay::kTableColumn || display == EDisplay::kTableCell || + display == EDisplay::kTableCaption; + + return form_is_table_part; +} + +Node::InsertionNotificationRequest HTMLFormElement::InsertedInto( + ContainerNode* insertion_point) { + HTMLElement::InsertedInto(insertion_point); + LogAddElementIfIsolatedWorldAndInDocument("form", methodAttr, actionAttr); + if (insertion_point->isConnected()) + GetDocument().DidAssociateFormControl(this); + return kInsertionDone; +} + +template <class T> +void NotifyFormRemovedFromTree(const T& elements, Node& root) { + for (const auto& element : elements) + element->FormRemovedFromTree(root); +} + +void HTMLFormElement::RemovedFrom(ContainerNode* insertion_point) { + // We don't need to take care of form association by 'form' content + // attribute becuse IdTargetObserver handles it. + if (has_elements_associated_by_parser_) { + Node& root = NodeTraversal::HighestAncestorOrSelf(*this); + if (!listed_elements_are_dirty_) { + ListedElement::List elements(ListedElements()); + NotifyFormRemovedFromTree(elements, root); + } else { + ListedElement::List elements; + CollectListedElements( + NodeTraversal::HighestAncestorOrSelf(*insertion_point), elements); + NotifyFormRemovedFromTree(elements, root); + CollectListedElements(root, elements); + NotifyFormRemovedFromTree(elements, root); + } + + if (!image_elements_are_dirty_) { + HeapVector<Member<HTMLImageElement>> images(ImageElements()); + NotifyFormRemovedFromTree(images, root); + } else { + HeapVector<Member<HTMLImageElement>> images; + CollectImageElements( + NodeTraversal::HighestAncestorOrSelf(*insertion_point), images); + NotifyFormRemovedFromTree(images, root); + CollectImageElements(root, images); + NotifyFormRemovedFromTree(images, root); + } + } + GetDocument().GetFormController().WillDeleteForm(this); + HTMLElement::RemovedFrom(insertion_point); +} + +void HTMLFormElement::HandleLocalEvents(Event& event) { + Node* target_node = event.target()->ToNode(); + if (event.eventPhase() != Event::kCapturingPhase && target_node && + target_node != this && + (event.type() == EventTypeNames::submit || + event.type() == EventTypeNames::reset)) { + event.stopPropagation(); + return; + } + HTMLElement::HandleLocalEvents(event); +} + +unsigned HTMLFormElement::length() const { + unsigned len = 0; + for (const auto& element : ListedElements()) { + if (element->IsEnumeratable()) + ++len; + } + return len; +} + +HTMLElement* HTMLFormElement::item(unsigned index) { + return elements()->item(index); +} + +void HTMLFormElement::SubmitImplicitly(Event* event, + bool from_implicit_submission_trigger) { + int submission_trigger_count = 0; + bool seen_default_button = false; + for (const auto& element : ListedElements()) { + if (!element->IsFormControlElement()) + continue; + HTMLFormControlElement* control = ToHTMLFormControlElement(element); + if (!seen_default_button && control->CanBeSuccessfulSubmitButton()) { + if (from_implicit_submission_trigger) + seen_default_button = true; + if (control->IsSuccessfulSubmitButton()) { + control->DispatchSimulatedClick(event); + return; + } + if (from_implicit_submission_trigger) { + // Default (submit) button is not activated; no implicit submission. + return; + } + } else if (control->CanTriggerImplicitSubmission()) { + ++submission_trigger_count; + } + } + if (from_implicit_submission_trigger && submission_trigger_count == 1) + PrepareForSubmission(event, nullptr); +} + +bool HTMLFormElement::ValidateInteractively() { + UseCounter::Count(GetDocument(), WebFeature::kFormValidationStarted); + for (const auto& element : ListedElements()) { + if (element->IsFormControlElement()) + ToHTMLFormControlElement(element)->HideVisibleValidationMessage(); + } + + HeapVector<Member<HTMLFormControlElement>> unhandled_invalid_controls; + if (!CheckInvalidControlsAndCollectUnhandled( + &unhandled_invalid_controls, kCheckValidityDispatchInvalidEvent)) + return true; + UseCounter::Count(GetDocument(), + WebFeature::kFormValidationAbortedSubmission); + // Because the form has invalid controls, we abort the form submission and + // show a validation message on a focusable form control. + + // Needs to update layout now because we'd like to call isFocusable(), which + // has !layoutObject()->needsLayout() assertion. + GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + + // Focus on the first focusable control and show a validation message. + for (const auto& unhandled : unhandled_invalid_controls) { + if (unhandled->IsFocusable()) { + unhandled->ShowValidationMessage(); + UseCounter::Count(GetDocument(), + WebFeature::kFormValidationShowedMessage); + break; + } + } + // Warn about all of unfocusable controls. + if (GetDocument().GetFrame()) { + for (const auto& unhandled : unhandled_invalid_controls) { + if (unhandled->IsFocusable()) + continue; + String message( + "An invalid form control with name='%name' is not focusable."); + message.Replace("%name", unhandled->GetName()); + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kRenderingMessageSource, kErrorMessageLevel, message)); + } + } + return false; +} + +void HTMLFormElement::PrepareForSubmission( + Event* event, + HTMLFormControlElement* submit_button) { + LocalFrame* frame = GetDocument().GetFrame(); + if (!frame || is_submitting_ || in_user_js_submit_event_) + return; + + if (!isConnected()) { + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kWarningMessageLevel, + "Form submission canceled because the form is not connected")); + return; + } + + if (GetDocument().IsSandboxed(kSandboxForms)) { + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Blocked form submission to '" + attributes_.Action() + + "' because the form's frame is sandboxed and the 'allow-forms' " + "permission is not set.")); + return; + } + + // https://github.com/whatwg/html/issues/2253 + for (const auto& element : ListedElements()) { + if (element->IsFormControlElement() && + ToHTMLFormControlElement(element)->BlocksFormSubmission()) { + UseCounter::Count(GetDocument(), + WebFeature::kFormSubmittedWithUnclosedFormControl); + if (RuntimeEnabledFeatures::UnclosedFormControlIsInvalidEnabled()) { + String tag_name = ToHTMLFormControlElement(element)->tagName(); + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Form submission failed, as the <" + tag_name + + "> element named " + "'" + + element->GetName() + + "' was implicitly closed by reaching " + "the end of the file. Please add an explicit end tag " + "('</" + + tag_name + ">')")); + DispatchEvent(Event::Create(EventTypeNames::error)); + return; + } + } + } + + bool skip_validation = !GetDocument().GetPage() || NoValidate(); + DCHECK(event); + if (submit_button && submit_button->FormNoValidate()) + skip_validation = true; + + UseCounter::Count(GetDocument(), WebFeature::kFormSubmissionStarted); + // Interactive validation must be done before dispatching the submit event. + if (!skip_validation && !ValidateInteractively()) + return; + + bool should_submit; + { + AutoReset<bool> submit_event_handler_scope(&in_user_js_submit_event_, true); + frame->Client()->DispatchWillSendSubmitEvent(this); + should_submit = + DispatchEvent(Event::CreateCancelableBubble(EventTypeNames::submit)) == + DispatchEventResult::kNotCanceled; + } + if (should_submit) { + planned_navigation_ = nullptr; + Submit(event, submit_button); + } + if (!planned_navigation_) + return; + AutoReset<bool> submit_scope(&is_submitting_, true); + ScheduleFormSubmission(planned_navigation_); + planned_navigation_ = nullptr; +} + +void HTMLFormElement::submitFromJavaScript() { + Submit(nullptr, nullptr); +} + +void HTMLFormElement::SubmitDialog(FormSubmission* form_submission) { + for (Node* node = this; node; node = node->ParentOrShadowHostNode()) { + if (auto* dialog = ToHTMLDialogElementOrNull(*node)) { + dialog->close(form_submission->Result()); + return; + } + } +} + +void HTMLFormElement::Submit(Event* event, + HTMLFormControlElement* submit_button) { + LocalFrameView* view = GetDocument().View(); + LocalFrame* frame = GetDocument().GetFrame(); + if (!view || !frame || !frame->GetPage()) + return; + + // https://html.spec.whatwg.org/multipage/forms.html#form-submission-algorithm + // 2. If form document is not connected, has no associated browsing context, + // or its active sandboxing flag set has its sandboxed forms browsing + // context flag set, then abort these steps without doing anything. + if (!isConnected()) { + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kWarningMessageLevel, + "Form submission canceled because the form is not connected")); + return; + } + + if (is_submitting_) + return; + + // Delay dispatching 'close' to dialog until done submitting. + EventQueueScope scope_for_dialog_close; + AutoReset<bool> submit_scope(&is_submitting_, true); + + if (event && !submit_button) { + // In a case of implicit submission without a submit button, 'submit' + // event handler might add a submit button. We search for a submit + // button again. + // TODO(tkent): Do we really need to activate such submit button? + for (const auto& listed_element : ListedElements()) { + if (!listed_element->IsFormControlElement()) + continue; + HTMLFormControlElement* control = + ToHTMLFormControlElement(listed_element); + DCHECK(!control->IsActivatedSubmit()); + if (control->IsSuccessfulSubmitButton()) { + submit_button = control; + break; + } + } + } + + FormSubmission* form_submission = + FormSubmission::Create(this, attributes_, event, submit_button); + // 'formdata' event handlers might disconnect the form. + if (RuntimeEnabledFeatures::FormDataEventEnabled() && !isConnected()) { + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kWarningMessageLevel, + "Form submission canceled because the form is not connected")); + return; + } + if (form_submission->Method() == FormSubmission::kDialogMethod) { + SubmitDialog(form_submission); + } else if (in_user_js_submit_event_) { + // Need to postpone the submission in order to make this cancelable by + // another submission request. + planned_navigation_ = form_submission; + } else { + // This runs JavaScript code if action attribute value is javascript: + // protocol. + ScheduleFormSubmission(form_submission); + } +} + +void HTMLFormElement::ConstructFormDataSet( + HTMLFormControlElement* submit_button, + FormData& form_data) { + // TODO(tkent): We might move the event dispatching later than the + // ListedElements iteration. + if (RuntimeEnabledFeatures::FormDataEventEnabled()) + DispatchEvent(FormDataEvent::Create(form_data)); + + if (submit_button) + submit_button->SetActivatedSubmit(true); + for (ListedElement* control : ListedElements()) { + DCHECK(control); + HTMLElement& element = ToHTMLElement(*control); + if (!element.IsDisabledFormControl()) + control->AppendToFormData(form_data); + if (auto* input = ToHTMLInputElementOrNull(element)) { + if (input->type() == InputTypeNames::password && + !input->value().IsEmpty()) + form_data.SetContainsPasswordData(true); + } + } + if (submit_button) + submit_button->SetActivatedSubmit(false); +} + +void HTMLFormElement::ScheduleFormSubmission(FormSubmission* submission) { + DCHECK(submission->Method() == FormSubmission::kPostMethod || + submission->Method() == FormSubmission::kGetMethod); + DCHECK(submission->Data()); + DCHECK(submission->Form()); + if (submission->Action().IsEmpty()) + return; + if (GetDocument().IsSandboxed(kSandboxForms)) { + // FIXME: This message should be moved off the console once a solution to + // https://bugs.webkit.org/show_bug.cgi?id=103274 exists. + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kSecurityMessageSource, kErrorMessageLevel, + "Blocked form submission to '" + submission->Action().ElidedString() + + "' because the form's frame is sandboxed and the 'allow-forms' " + "permission is not set.")); + return; + } + + if (!GetDocument().GetContentSecurityPolicy()->AllowFormAction( + submission->Action())) { + return; + } + + if (submission->Action().ProtocolIsJavaScript()) { + GetDocument() + .GetFrame() + ->GetScriptController() + .ExecuteScriptIfJavaScriptURL(submission->Action(), this); + return; + } + + Frame* target_frame = GetDocument().GetFrame()->FindFrameForNavigation( + submission->Target(), *GetDocument().GetFrame(), + submission->RequestURL()); + if (!target_frame) { + target_frame = GetDocument().GetFrame(); + } else { + submission->ClearTarget(); + } + if (!target_frame->GetPage()) + return; + + UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted); + if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(), + submission->Action())) { + UseCounter::Count(GetDocument().GetFrame(), + WebFeature::kMixedContentFormsSubmitted); + } + + // TODO(lukasza): Investigate if the code below can uniformly handle remote + // and local frames (i.e. by calling virtual Frame::navigate from a timer). + // See also https://goo.gl/95d2KA. + if (target_frame->IsLocalFrame()) { + ToLocalFrame(target_frame) + ->GetNavigationScheduler() + .ScheduleFormSubmission(&GetDocument(), submission); + } else { + FrameLoadRequest frame_load_request = + submission->CreateFrameLoadRequest(&GetDocument()); + frame_load_request.GetResourceRequest().SetHasUserGesture( + Frame::HasTransientUserActivation(GetDocument().GetFrame())); + ToRemoteFrame(target_frame)->Navigate(frame_load_request); + } +} + +void HTMLFormElement::reset() { + LocalFrame* frame = GetDocument().GetFrame(); + if (is_in_reset_function_ || !frame) + return; + + is_in_reset_function_ = true; + + if (DispatchEvent(Event::CreateCancelableBubble(EventTypeNames::reset)) != + DispatchEventResult::kNotCanceled) { + is_in_reset_function_ = false; + return; + } + + // Copy the element list because |reset()| implementation can update DOM + // structure. + ListedElement::List elements(ListedElements()); + for (const auto& element : elements) { + if (element->IsFormControlElement()) + ToHTMLFormControlElement(element)->Reset(); + } + + is_in_reset_function_ = false; +} + +void HTMLFormElement::ParseAttribute( + const AttributeModificationParams& params) { + const QualifiedName& name = params.name; + if (name == actionAttr) { + attributes_.ParseAction(params.new_value); + LogUpdateAttributeIfIsolatedWorldAndInDocument("form", params); + + // If we're not upgrading insecure requests, and the new action attribute is + // pointing to an insecure "action" location from a secure page it is marked + // as "passive" mixed content. + if (GetDocument().GetInsecureRequestPolicy() & kUpgradeInsecureRequests) + return; + KURL action_url = GetDocument().CompleteURL( + attributes_.Action().IsEmpty() ? GetDocument().Url().GetString() + : attributes_.Action()); + if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(), + action_url)) { + UseCounter::Count(GetDocument().GetFrame(), + WebFeature::kMixedContentFormPresent); + } + } else if (name == targetAttr) { + attributes_.SetTarget(params.new_value); + } else if (name == methodAttr) { + attributes_.UpdateMethodType(params.new_value); + } else if (name == enctypeAttr) { + attributes_.UpdateEncodingType(params.new_value); + } else if (name == accept_charsetAttr) { + attributes_.SetAcceptCharset(params.new_value); + } else { + HTMLElement::ParseAttribute(params); + } +} + +void HTMLFormElement::Associate(ListedElement& e) { + listed_elements_are_dirty_ = true; + listed_elements_.clear(); + if (ToHTMLElement(e).FastHasAttribute(formAttr)) + has_elements_associated_by_form_attribute_ = true; +} + +void HTMLFormElement::Disassociate(ListedElement& e) { + listed_elements_are_dirty_ = true; + listed_elements_.clear(); + RemoveFromPastNamesMap(ToHTMLElement(e)); +} + +bool HTMLFormElement::IsURLAttribute(const Attribute& attribute) const { + return attribute.GetName() == actionAttr || + HTMLElement::IsURLAttribute(attribute); +} + +bool HTMLFormElement::HasLegalLinkAttribute(const QualifiedName& name) const { + return name == actionAttr || HTMLElement::HasLegalLinkAttribute(name); +} + +void HTMLFormElement::Associate(HTMLImageElement& e) { + image_elements_are_dirty_ = true; + image_elements_.clear(); +} + +void HTMLFormElement::Disassociate(HTMLImageElement& e) { + image_elements_are_dirty_ = true; + image_elements_.clear(); + RemoveFromPastNamesMap(e); +} + +void HTMLFormElement::DidAssociateByParser() { + if (!did_finish_parsing_children_) + return; + has_elements_associated_by_parser_ = true; + UseCounter::Count(GetDocument(), WebFeature::kFormAssociationByParser); +} + +HTMLFormControlsCollection* HTMLFormElement::elements() { + return EnsureCachedCollection<HTMLFormControlsCollection>(kFormControls); +} + +void HTMLFormElement::CollectListedElements( + Node& root, + ListedElement::List& elements) const { + elements.clear(); + for (HTMLElement& element : Traversal<HTMLElement>::StartsAfter(root)) { + ListedElement* listed_element = nullptr; + if (element.IsFormControlElement()) + listed_element = ToHTMLFormControlElement(&element); + else if (auto* object = ToHTMLObjectElementOrNull(element)) + listed_element = object; + else + continue; + if (listed_element->Form() == this) + elements.push_back(listed_element); + } +} + +// This function should be const conceptually. However we update some fields +// because of lazy evaluation. +const ListedElement::List& HTMLFormElement::ListedElements() const { + if (!listed_elements_are_dirty_) + return listed_elements_; + HTMLFormElement* mutable_this = const_cast<HTMLFormElement*>(this); + Node* scope = mutable_this; + if (has_elements_associated_by_parser_) + scope = &NodeTraversal::HighestAncestorOrSelf(*mutable_this); + if (isConnected() && has_elements_associated_by_form_attribute_) + scope = &GetTreeScope().RootNode(); + DCHECK(scope); + CollectListedElements(*scope, mutable_this->listed_elements_); + mutable_this->listed_elements_are_dirty_ = false; + return listed_elements_; +} + +void HTMLFormElement::CollectImageElements( + Node& root, + HeapVector<Member<HTMLImageElement>>& elements) { + elements.clear(); + for (HTMLImageElement& image : + Traversal<HTMLImageElement>::StartsAfter(root)) { + if (image.formOwner() == this) + elements.push_back(&image); + } +} + +const HeapVector<Member<HTMLImageElement>>& HTMLFormElement::ImageElements() { + if (!image_elements_are_dirty_) + return image_elements_; + CollectImageElements(has_elements_associated_by_parser_ + ? NodeTraversal::HighestAncestorOrSelf(*this) + : *this, + image_elements_); + image_elements_are_dirty_ = false; + return image_elements_; +} + +String HTMLFormElement::GetName() const { + return GetNameAttribute(); +} + +bool HTMLFormElement::NoValidate() const { + return FastHasAttribute(novalidateAttr); +} + +String HTMLFormElement::action() const { + Document& document = GetDocument(); + KURL action_url = document.CompleteURL(attributes_.Action().IsEmpty() + ? document.Url().GetString() + : attributes_.Action()); + return action_url.GetString(); +} + +void HTMLFormElement::setAction(const AtomicString& value) { + setAttribute(actionAttr, value); +} + +void HTMLFormElement::setEnctype(const AtomicString& value) { + setAttribute(enctypeAttr, value); +} + +String HTMLFormElement::method() const { + return FormSubmission::Attributes::MethodString(attributes_.Method()); +} + +void HTMLFormElement::setMethod(const AtomicString& value) { + setAttribute(methodAttr, value); +} + +HTMLFormControlElement* HTMLFormElement::FindDefaultButton() const { + for (const auto& element : ListedElements()) { + if (!element->IsFormControlElement()) + continue; + HTMLFormControlElement* control = ToHTMLFormControlElement(element); + if (control->CanBeSuccessfulSubmitButton()) + return control; + } + return nullptr; +} + +bool HTMLFormElement::checkValidity() { + return !CheckInvalidControlsAndCollectUnhandled( + nullptr, kCheckValidityDispatchInvalidEvent); +} + +bool HTMLFormElement::CheckInvalidControlsAndCollectUnhandled( + HeapVector<Member<HTMLFormControlElement>>* unhandled_invalid_controls, + CheckValidityEventBehavior event_behavior) { + // Copy listedElements because event handlers called from + // HTMLFormControlElement::checkValidity() might change listedElements. + const ListedElement::List& listed_elements = ListedElements(); + HeapVector<Member<ListedElement>> elements; + elements.ReserveCapacity(listed_elements.size()); + for (const auto& element : listed_elements) + elements.push_back(element); + int invalid_controls_count = 0; + for (const auto& element : elements) { + if (element->Form() == this && element->IsFormControlElement()) { + HTMLFormControlElement* control = ToHTMLFormControlElement(element); + if (control->IsSubmittableElement() && + !control->checkValidity(unhandled_invalid_controls, event_behavior) && + control->formOwner() == this) { + ++invalid_controls_count; + if (!unhandled_invalid_controls && + event_behavior == kCheckValidityDispatchNoEvent) + return true; + } + } + } + return invalid_controls_count; +} + +bool HTMLFormElement::reportValidity() { + return ValidateInteractively(); +} + +Element* HTMLFormElement::ElementFromPastNamesMap( + const AtomicString& past_name) { + if (past_name.IsEmpty() || !past_names_map_) + return nullptr; + Element* element = past_names_map_->at(past_name); +#if DCHECK_IS_ON() + if (!element) + return nullptr; + SECURITY_DCHECK(ToHTMLElement(element)->formOwner() == this); + if (IsHTMLImageElement(*element)) { + SECURITY_DCHECK(ImageElements().Find(element) != kNotFound); + } else if (IsHTMLObjectElement(*element)) { + SECURITY_DCHECK(ListedElements().Find(ToHTMLObjectElement(element)) != + kNotFound); + } else { + SECURITY_DCHECK(ListedElements().Find(ToHTMLFormControlElement(element)) != + kNotFound); + } +#endif + return element; +} + +void HTMLFormElement::AddToPastNamesMap(Element* element, + const AtomicString& past_name) { + if (past_name.IsEmpty()) + return; + if (!past_names_map_) + past_names_map_ = new PastNamesMap; + past_names_map_->Set(past_name, element); +} + +void HTMLFormElement::RemoveFromPastNamesMap(HTMLElement& element) { + if (!past_names_map_) + return; + for (auto& it : *past_names_map_) { + if (it.value == &element) { + it.value = nullptr; + // Keep looping. Single element can have multiple names. + } + } +} + +void HTMLFormElement::GetNamedElements( + const AtomicString& name, + HeapVector<Member<Element>>& named_items) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem + elements()->NamedItems(name, named_items); + + Element* element_from_past = ElementFromPastNamesMap(name); + if (named_items.size() && named_items.front() != element_from_past) { + AddToPastNamesMap(named_items.front().Get(), name); + } else if (element_from_past && named_items.IsEmpty()) { + named_items.push_back(element_from_past); + UseCounter::Count(GetDocument(), + WebFeature::kFormNameAccessForPastNamesMap); + } +} + +bool HTMLFormElement::ShouldAutocomplete() const { + return !DeprecatedEqualIgnoringCase(FastGetAttribute(autocompleteAttr), + "off"); +} + +void HTMLFormElement::FinishParsingChildren() { + HTMLElement::FinishParsingChildren(); + GetDocument().GetFormController().RestoreControlStateIn(*this); + did_finish_parsing_children_ = true; +} + +void HTMLFormElement::CloneNonAttributePropertiesFrom(const Element& source, + CloneChildrenFlag flag) { + was_demoted_ = ToHTMLFormElement(source).was_demoted_; + HTMLElement::CloneNonAttributePropertiesFrom(source, flag); +} + +void HTMLFormElement::AnonymousNamedGetter( + const AtomicString& name, + RadioNodeListOrElement& return_value) { + // Call getNamedElements twice, first time check if it has a value + // and let HTMLFormElement update its cache. + // See issue: 867404 + { + HeapVector<Member<Element>> elements; + GetNamedElements(name, elements); + if (elements.IsEmpty()) + return; + } + + // Second call may return different results from the first call, + // but if the first the size cannot be zero. + HeapVector<Member<Element>> elements; + GetNamedElements(name, elements); + DCHECK(!elements.IsEmpty()); + + bool only_match_img = + !elements.IsEmpty() && IsHTMLImageElement(*elements.front()); + if (only_match_img) { + UseCounter::Count(GetDocument(), + WebFeature::kFormNameAccessForImageElement); + // The following code has performance impact, but it should be small + // because <img> access via <form> name getter is rarely used. + for (auto& element : elements) { + if (IsHTMLImageElement(*element) && !element->IsDescendantOf(this)) { + UseCounter::Count( + GetDocument(), + WebFeature::kFormNameAccessForNonDescendantImageElement); + break; + } + } + } + if (elements.size() == 1) { + return_value.SetElement(elements.at(0)); + return; + } + + return_value.SetRadioNodeList(GetRadioNodeList(name, only_match_img)); +} + +void HTMLFormElement::SetDemoted(bool demoted) { + if (demoted) + UseCounter::Count(GetDocument(), WebFeature::kDemotedFormElement); + was_demoted_ = demoted; +} + +void HTMLFormElement::InvalidateDefaultButtonStyle() const { + for (const auto& control : ListedElements()) { + if (!control->IsFormControlElement()) + continue; + if (ToHTMLFormControlElement(control)->CanBeSuccessfulSubmitButton()) { + ToHTMLFormControlElement(control)->PseudoStateChanged( + CSSSelector::kPseudoDefault); + } + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h new file mode 100644 index 00000000000..6ba89f9f2fc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/core/html/forms/radio_button_group_scope.h" +#include "third_party/blink/renderer/core/html/html_element.h" +#include "third_party/blink/renderer/core/loader/form_submission.h" + +namespace blink { + +class Event; +class ListedElement; +class HTMLFormControlElement; +class HTMLFormControlsCollection; +class HTMLImageElement; +class RadioNodeListOrElement; + +class CORE_EXPORT HTMLFormElement final : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLFormElement* Create(Document&); + ~HTMLFormElement() override; + void Trace(blink::Visitor*) override; + + HTMLFormControlsCollection* elements(); + void GetNamedElements(const AtomicString&, HeapVector<Member<Element>>&); + + unsigned length() const; + HTMLElement* item(unsigned index); + + String action() const; + void setAction(const AtomicString&); + + String enctype() const { return attributes_.EncodingType(); } + void setEnctype(const AtomicString&); + + String encoding() const { return attributes_.EncodingType(); } + void setEncoding(const AtomicString& value) { setEnctype(value); } + + bool ShouldAutocomplete() const; + + void Associate(ListedElement&); + void Disassociate(ListedElement&); + void Associate(HTMLImageElement&); + void Disassociate(HTMLImageElement&); + void DidAssociateByParser(); + + void PrepareForSubmission(Event*, HTMLFormControlElement* submit_button); + void submitFromJavaScript(); + void reset(); + + void SetDemoted(bool); + + void SubmitImplicitly(Event*, bool from_implicit_submission_trigger); + + String GetName() const; + + bool NoValidate() const; + + const AtomicString& Action() const; + + String method() const; + void setMethod(const AtomicString&); + + // Find the 'default button.' + // https://html.spec.whatwg.org/multipage/forms.html#default-button + HTMLFormControlElement* FindDefaultButton() const; + + bool checkValidity(); + bool reportValidity(); + bool MatchesValidityPseudoClasses() const final; + bool IsValidElement() final; + + RadioButtonGroupScope& GetRadioButtonGroupScope() { + return radio_button_group_scope_; + } + + const ListedElement::List& ListedElements() const; + const HeapVector<Member<HTMLImageElement>>& ImageElements(); + + void AnonymousNamedGetter(const AtomicString& name, RadioNodeListOrElement&); + void InvalidateDefaultButtonStyle() const; + + // 'construct the form data set' + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set + void ConstructFormDataSet(HTMLFormControlElement* submit_button, + FormData& form_data); + + private: + explicit HTMLFormElement(Document&); + + bool LayoutObjectIsNeeded(const ComputedStyle&) const override; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void RemovedFrom(ContainerNode*) override; + void FinishParsingChildren() override; + + void HandleLocalEvents(Event&) override; + + void ParseAttribute(const AttributeModificationParams&) override; + bool IsURLAttribute(const Attribute&) const override; + bool HasLegalLinkAttribute(const QualifiedName&) const override; + + NamedItemType GetNamedItemType() const override { + return NamedItemType::kName; + } + + void CloneNonAttributePropertiesFrom(const Element&, + CloneChildrenFlag) override; + + void SubmitDialog(FormSubmission*); + void Submit(Event*, HTMLFormControlElement* submit_button); + + void ScheduleFormSubmission(FormSubmission*); + + void CollectListedElements(Node& root, ListedElement::List&) const; + void CollectImageElements(Node& root, HeapVector<Member<HTMLImageElement>>&); + + // Returns true if the submission should proceed. + bool ValidateInteractively(); + + // Validates each of the controls, and stores controls of which 'invalid' + // event was not canceled to the specified vector. Returns true if there + // are any invalid controls in this form. + bool CheckInvalidControlsAndCollectUnhandled( + HeapVector<Member<HTMLFormControlElement>>*, + CheckValidityEventBehavior); + + Element* ElementFromPastNamesMap(const AtomicString&); + void AddToPastNamesMap(Element*, const AtomicString& past_name); + void RemoveFromPastNamesMap(HTMLElement&); + + typedef HeapHashMap<AtomicString, Member<Element>> PastNamesMap; + + FormSubmission::Attributes attributes_; + Member<PastNamesMap> past_names_map_; + + RadioButtonGroupScope radio_button_group_scope_; + + // Do not access m_listedElements directly. Use listedElements() + // instead. + ListedElement::List listed_elements_; + // Do not access m_imageElements directly. Use imageElements() instead. + HeapVector<Member<HTMLImageElement>> image_elements_; + + // https://html.spec.whatwg.org/multipage/forms.html#planned-navigation + // Unlike the specification, we use this only for web-exposed submit() + // function in 'submit' event handler. + Member<FormSubmission> planned_navigation_; + + bool is_submitting_ = false; + bool in_user_js_submit_event_ = false; + + bool listed_elements_are_dirty_ : 1; + bool image_elements_are_dirty_ : 1; + bool has_elements_associated_by_parser_ : 1; + bool has_elements_associated_by_form_attribute_ : 1; + bool did_finish_parsing_children_ : 1; + bool is_in_reset_function_ : 1; + bool was_demoted_ : 1; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_FORM_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl new file mode 100644 index 00000000000..8a830e253b2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_form_element.idl @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * + * 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. + */ + +// https://html.spec.whatwg.org/#the-form-element + +[ + HTMLConstructor, + OverrideBuiltins +] interface HTMLFormElement : HTMLElement { + [CEReactions, Reflect=accept_charset] attribute DOMString acceptCharset; + [CEReactions, URL] attribute DOMString action; + [CEReactions, Reflect, ReflectOnly=("on","off"), ReflectMissing="on", ReflectInvalid="on"] attribute DOMString autocomplete; + [CEReactions, CustomElementCallbacks] attribute DOMString enctype; + [CEReactions, CustomElementCallbacks] attribute DOMString encoding; + [CEReactions, CustomElementCallbacks] attribute DOMString method; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute boolean noValidate; + [CEReactions, Reflect] attribute DOMString target; + + readonly attribute HTMLFormControlsCollection elements; + readonly attribute long length; + [ImplementedAs=item] getter Element (unsigned long index); + // FIXME: This getter should not have [NotEnumerable]. + [NotEnumerable] getter (RadioNodeList or Element) (DOMString name); + + [ImplementedAs=submitFromJavaScript] void submit(); + [CEReactions, CustomElementCallbacks] void reset(); + boolean checkValidity(); + boolean reportValidity(); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc new file mode 100644 index 00000000000..cb0f2b5c862 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.cc @@ -0,0 +1,1950 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + * rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * Copyright (C) 2012 Samsung Electronics. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" + +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_scroll_into_view_params.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h" +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/css_property_names.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/id_target_observer.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/dom/sync_reattach_context.h" +#include "third_party/blink/renderer/core/dom/v0_insertion_point.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h" +#include "third_party/blink/renderer/core/events/before_text_inserted_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/core/html/forms/file_input_type.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/search_input_type.h" +#include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/core/html/html_image_loader.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/language.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" + +namespace blink { + +using ValueMode = InputType::ValueMode; +using namespace HTMLNames; + +class ListAttributeTargetObserver : public IdTargetObserver { + public: + static ListAttributeTargetObserver* Create(const AtomicString& id, + HTMLInputElement*); + void Trace(blink::Visitor*) override; + void IdTargetChanged() override; + + private: + ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement*); + + Member<HTMLInputElement> element_; +}; + +const int kDefaultSize = 20; + +HTMLInputElement::HTMLInputElement(Document& document, + const CreateElementFlags flags) + : TextControlElement(inputTag, document), + size_(kDefaultSize), + has_dirty_value_(false), + is_checked_(false), + dirty_checkedness_(false), + is_indeterminate_(false), + is_activated_submit_(false), + autocomplete_(kUninitialized), + has_non_empty_list_(false), + state_restored_(false), + parsing_in_progress_(flags.IsCreatedByParser()), + can_receive_dropped_files_(false), + should_reveal_password_(false), + needs_to_update_view_value_(true), + is_placeholder_visible_(false), + has_been_password_field_(false), + // |input_type_| 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'. + input_type_(flags.IsCreatedByParser() ? nullptr + : InputType::CreateText(*this)), + input_type_view_(input_type_ ? input_type_->CreateView() : nullptr) { + SetHasCustomStyleCallbacks(); +} + +HTMLInputElement* HTMLInputElement::Create(Document& document, + const CreateElementFlags flags) { + auto* input_element = new HTMLInputElement(document, flags); + if (!flags.IsCreatedByParser()) { + DCHECK(input_element->input_type_view_->NeedsShadowSubtree()); + input_element->CreateUserAgentShadowRoot(); + input_element->CreateShadowSubtree(); + } + return input_element; +} + +void HTMLInputElement::Trace(blink::Visitor* visitor) { + visitor->Trace(input_type_); + visitor->Trace(input_type_view_); + visitor->Trace(list_attribute_target_observer_); + visitor->Trace(image_loader_); + TextControlElement::Trace(visitor); +} + +bool HTMLInputElement::HasPendingActivity() const { + return ImageLoader() && ImageLoader()->HasPendingActivity(); +} + +HTMLImageLoader& HTMLInputElement::EnsureImageLoader() { + if (!image_loader_) + image_loader_ = HTMLImageLoader::Create(this); + return *image_loader_; +} + +HTMLInputElement::~HTMLInputElement() = default; + +const AtomicString& HTMLInputElement::GetName() const { + return name_.IsNull() ? g_empty_atom : name_; +} + +Vector<FileChooserFileInfo> +HTMLInputElement::FilesFromFileInputFormControlState( + const FormControlState& state) { + return FileInputType::FilesFromFormControlState(state); +} + +bool HTMLInputElement::ShouldAutocomplete() const { + if (autocomplete_ != kUninitialized) + return autocomplete_ == kOn; + return TextControlElement::ShouldAutocomplete(); +} + +bool HTMLInputElement::IsValidValue(const String& value) const { + if (!input_type_->CanSetStringValue()) { + NOTREACHED(); + return false; + } + return !input_type_->TypeMismatchFor(value) && + !input_type_->StepMismatch(value) && + !input_type_->RangeUnderflow(value) && + !input_type_->RangeOverflow(value) && + !TooLong(value, kIgnoreDirtyFlag) && + !TooShort(value, kIgnoreDirtyFlag) && + !input_type_->PatternMismatch(value) && + !input_type_->ValueMissing(value); +} + +bool HTMLInputElement::TooLong() const { + return willValidate() && TooLong(value(), kCheckDirtyFlag); +} + +bool HTMLInputElement::TooShort() const { + return willValidate() && TooShort(value(), kCheckDirtyFlag); +} + +bool HTMLInputElement::TypeMismatch() const { + return willValidate() && input_type_->TypeMismatch(); +} + +bool HTMLInputElement::ValueMissing() const { + return willValidate() && input_type_->ValueMissing(value()); +} + +bool HTMLInputElement::HasBadInput() const { + return willValidate() && input_type_view_->HasBadInput(); +} + +bool HTMLInputElement::PatternMismatch() const { + return willValidate() && input_type_->PatternMismatch(value()); +} + +bool HTMLInputElement::TooLong(const String& value, + NeedsToCheckDirtyFlag check) const { + return input_type_->TooLong(value, check); +} + +bool HTMLInputElement::TooShort(const String& value, + NeedsToCheckDirtyFlag check) const { + return input_type_->TooShort(value, check); +} + +bool HTMLInputElement::RangeUnderflow() const { + return willValidate() && input_type_->RangeUnderflow(value()); +} + +bool HTMLInputElement::RangeOverflow() const { + return willValidate() && input_type_->RangeOverflow(value()); +} + +String HTMLInputElement::validationMessage() const { + if (!willValidate()) + return String(); + + if (CustomError()) + return CustomValidationMessage(); + + return input_type_->ValidationMessage(*input_type_view_).first; +} + +String HTMLInputElement::ValidationSubMessage() const { + if (!willValidate() || CustomError()) + return String(); + return input_type_->ValidationMessage(*input_type_view_).second; +} + +double HTMLInputElement::Minimum() const { + return input_type_->Minimum(); +} + +double HTMLInputElement::Maximum() const { + return input_type_->Maximum(); +} + +bool HTMLInputElement::StepMismatch() const { + return willValidate() && input_type_->StepMismatch(value()); +} + +bool HTMLInputElement::GetAllowedValueStep(Decimal* step) const { + return input_type_->GetAllowedValueStep(step); +} + +StepRange HTMLInputElement::CreateStepRange( + AnyStepHandling any_step_handling) const { + return input_type_->CreateStepRange(any_step_handling); +} + +Decimal HTMLInputElement::FindClosestTickMarkValue(const Decimal& value) { + return input_type_->FindClosestTickMarkValue(value); +} + +void HTMLInputElement::stepUp(int n, ExceptionState& exception_state) { + input_type_->StepUp(n, exception_state); +} + +void HTMLInputElement::stepDown(int n, ExceptionState& exception_state) { + input_type_->StepUp(-1.0 * n, exception_state); +} + +void HTMLInputElement::blur() { + input_type_view_->Blur(); +} + +void HTMLInputElement::DefaultBlur() { + TextControlElement::blur(); +} + +bool HTMLInputElement::HasCustomFocusLogic() const { + return input_type_view_->HasCustomFocusLogic(); +} + +bool HTMLInputElement::IsKeyboardFocusable() const { + return input_type_->IsKeyboardFocusable(); +} + +bool HTMLInputElement::ShouldShowFocusRingOnMouseFocus() const { + return input_type_->ShouldShowFocusRingOnMouseFocus(); +} + +void HTMLInputElement::UpdateFocusAppearanceWithOptions( + SelectionBehaviorOnFocus selection_behavior, + const FocusOptions& options) { + if (IsTextField()) { + switch (selection_behavior) { + case SelectionBehaviorOnFocus::kReset: + select(); + break; + case SelectionBehaviorOnFocus::kRestore: + RestoreCachedSelection(); + break; + case SelectionBehaviorOnFocus::kNone: + return; + } + // TODO(tkent): scrollRectToVisible is a workaround of a bug of + // FrameSelection::revealSelection(). It doesn't scroll correctly in a + // case of RangeSelection. crbug.com/443061. + GetDocument().EnsurePaintLocationDataValidForNode(this); + if (!options.preventScroll()) { + if (GetLayoutObject()) { + GetLayoutObject()->ScrollRectToVisible(BoundingBoxForScrollIntoView(), + WebScrollIntoViewParams()); + } + if (GetDocument().GetFrame()) + GetDocument().GetFrame()->Selection().RevealSelection(); + } + } else { + TextControlElement::UpdateFocusAppearanceWithOptions(selection_behavior, + options); + } +} + +void HTMLInputElement::EndEditing() { + DCHECK(GetDocument().IsActive()); + if (!GetDocument().IsActive()) + return; + + if (!IsTextField()) + return; + + LocalFrame* frame = GetDocument().GetFrame(); + frame->GetSpellChecker().DidEndEditingOnTextField(this); + frame->GetPage()->GetChromeClient().DidEndEditingOnTextField(*this); +} + +void HTMLInputElement::DispatchFocusInEvent( + const AtomicString& event_type, + Element* old_focused_element, + WebFocusType type, + InputDeviceCapabilities* source_capabilities) { + if (event_type == EventTypeNames::DOMFocusIn) + input_type_view_->HandleFocusInEvent(old_focused_element, type); + HTMLFormControlElementWithState::DispatchFocusInEvent( + event_type, old_focused_element, type, source_capabilities); +} + +void HTMLInputElement::HandleBlurEvent() { + input_type_view_->HandleBlurEvent(); +} + +void HTMLInputElement::setType(const AtomicString& type) { + setAttribute(typeAttr, type); +} + +void HTMLInputElement::InitializeTypeInParsing() { + DCHECK(parsing_in_progress_); + DCHECK(!input_type_); + DCHECK(!input_type_view_); + + const AtomicString& new_type_name = + InputType::NormalizeTypeName(FastGetAttribute(typeAttr)); + input_type_ = InputType::Create(*this, new_type_name); + input_type_view_ = input_type_->CreateView(); + String default_value = FastGetAttribute(valueAttr); + if (input_type_->GetValueMode() == ValueMode::kValue) + non_attribute_value_ = SanitizeValue(default_value); + has_been_password_field_ |= new_type_name == InputTypeNames::password; + + if (input_type_view_->NeedsShadowSubtree()) { + CreateUserAgentShadowRoot(); + CreateShadowSubtree(); + } + + SetNeedsWillValidateCheck(); + + if (!default_value.IsNull()) + input_type_->WarnIfValueIsInvalid(default_value); + + input_type_view_->UpdateView(); +} + +void HTMLInputElement::UpdateType() { + DCHECK(input_type_); + DCHECK(input_type_view_); + + const AtomicString& new_type_name = + InputType::NormalizeTypeName(FastGetAttribute(typeAttr)); + if (input_type_->FormControlType() == new_type_name) + return; + + InputType* new_type = InputType::Create(*this, new_type_name); + RemoveFromRadioButtonGroup(); + + ValueMode old_value_mode = input_type_->GetValueMode(); + bool did_respect_height_and_width = + input_type_->ShouldRespectHeightAndWidthAttributes(); + bool could_be_successful_submit_button = CanBeSuccessfulSubmitButton(); + + input_type_view_->DestroyShadowSubtree(); + DropInnerEditorElement(); + LazyReattachIfAttached(); + + if (input_type_->SupportsRequired() != new_type->SupportsRequired() && + IsRequired()) { + PseudoStateChanged(CSSSelector::kPseudoRequired); + PseudoStateChanged(CSSSelector::kPseudoOptional); + } + if (input_type_->SupportsReadOnly() != new_type->SupportsReadOnly()) { + PseudoStateChanged(CSSSelector::kPseudoReadOnly); + PseudoStateChanged(CSSSelector::kPseudoReadWrite); + } + if (input_type_->IsCheckable() != new_type->IsCheckable()) { + PseudoStateChanged(CSSSelector::kPseudoChecked); + } + PseudoStateChanged(CSSSelector::kPseudoIndeterminate); + if (input_type_->IsSteppable() || new_type->IsSteppable()) { + PseudoStateChanged(CSSSelector::kPseudoInRange); + PseudoStateChanged(CSSSelector::kPseudoOutOfRange); + } + + bool placeholder_changed = + input_type_->SupportsPlaceholder() != new_type->SupportsPlaceholder(); + + has_been_password_field_ |= new_type_name == InputTypeNames::password; + + input_type_ = new_type; + input_type_view_ = input_type_->CreateView(); + if (input_type_view_->NeedsShadowSubtree()) { + EnsureUserAgentShadowRoot(); + CreateShadowSubtree(); + } + + SetNeedsWillValidateCheck(); + + if (placeholder_changed) { + // We need to update the UA shadow and then the placeholder visibility flag + // here. Otherwise it would happen as part of attaching the layout tree + // which would be too late in order to make style invalidation work for + // the upcoming frame. + UpdatePlaceholderText(); + UpdatePlaceholderVisibility(); + PseudoStateChanged(CSSSelector::kPseudoPlaceholderShown); + } + + ValueMode new_value_mode = input_type_->GetValueMode(); + + // https://html.spec.whatwg.org/multipage/forms.html#input-type-change + // + // 1. If the previous state of the element's type attribute put the value IDL + // attribute in the value mode, and the element's value is not the empty + // string, and the new state of the element's type attribute puts the value + // IDL attribute in either the default mode or the default/on mode, then set + // the element's value content attribute to the element's value. + if (old_value_mode == ValueMode::kValue && + (new_value_mode == ValueMode::kDefault || + new_value_mode == ValueMode::kDefaultOn)) { + if (HasDirtyValue()) + setAttribute(valueAttr, AtomicString(non_attribute_value_)); + non_attribute_value_ = String(); + has_dirty_value_ = false; + } + // 2. Otherwise, if the previous state of the element's type attribute put the + // value IDL attribute in any mode other than the value mode, and the new + // state of the element's type attribute puts the value IDL attribute in the + // value mode, then set the value of the element to the value of the value + // content attribute, if there is one, or the empty string otherwise, and then + // set the control's dirty value flag to false. + else if (old_value_mode != ValueMode::kValue && + new_value_mode == ValueMode::kValue) { + AtomicString value_string = FastGetAttribute(valueAttr); + input_type_->WarnIfValueIsInvalid(value_string); + non_attribute_value_ = SanitizeValue(value_string); + has_dirty_value_ = false; + } + // 3. Otherwise, if the previous state of the element's type attribute put the + // value IDL attribute in any mode other than the filename mode, and the new + // state of the element's type attribute puts the value IDL attribute in the + // filename mode, then set the value of the element to the empty string. + else if (old_value_mode != ValueMode::kFilename && + new_value_mode == ValueMode::kFilename) { + non_attribute_value_ = String(); + has_dirty_value_ = false; + + } else { + // ValueMode wasn't changed, or kDefault <-> kDefaultOn. + if (!HasDirtyValue()) { + String default_value = FastGetAttribute(valueAttr); + if (!default_value.IsNull()) + input_type_->WarnIfValueIsInvalid(default_value); + } + + if (new_value_mode == ValueMode::kValue) { + String new_value = SanitizeValue(non_attribute_value_); + if (!EqualIgnoringNullity(new_value, non_attribute_value_)) { + if (HasDirtyValue()) + setValue(new_value); + else + SetNonDirtyValue(new_value); + } + } + } + + needs_to_update_view_value_ = true; + input_type_view_->UpdateView(); + + if (did_respect_height_and_width != + input_type_->ShouldRespectHeightAndWidthAttributes()) { + DCHECK(GetElementData()); + AttributeCollection attributes = AttributesWithoutUpdate(); + if (const Attribute* height = attributes.Find(heightAttr)) { + TextControlElement::AttributeChanged(AttributeModificationParams( + heightAttr, height->Value(), height->Value(), + AttributeModificationReason::kDirectly)); + } + if (const Attribute* width = attributes.Find(widthAttr)) { + TextControlElement::AttributeChanged( + AttributeModificationParams(widthAttr, width->Value(), width->Value(), + AttributeModificationReason::kDirectly)); + } + if (const Attribute* align = attributes.Find(alignAttr)) { + TextControlElement::AttributeChanged( + AttributeModificationParams(alignAttr, align->Value(), align->Value(), + AttributeModificationReason::kDirectly)); + } + } + + // UA Shadow tree was recreated. We need to set selection again. We do it + // later in order to avoid force layout. + if (GetDocument().FocusedElement() == this) + GetDocument().UpdateFocusAppearanceLater(); + + // TODO(tkent): Should we dispatch a change event? + ClearValueBeforeFirstUserEdit(); + + AddToRadioButtonGroup(); + + SetNeedsValidityCheck(); + if ((could_be_successful_submit_button || CanBeSuccessfulSubmitButton()) && + formOwner() && isConnected()) + formOwner()->InvalidateDefaultButtonStyle(); + NotifyFormStateChanged(); +} + +void HTMLInputElement::SubtreeHasChanged() { + input_type_view_->SubtreeHasChanged(); + // When typing in an input field, childrenChanged is not called, so we need to + // force the directionality check. + CalculateAndAdjustDirectionality(); +} + +const AtomicString& HTMLInputElement::FormControlType() const { + return input_type_->FormControlType(); +} + +bool HTMLInputElement::ShouldSaveAndRestoreFormControlState() const { + if (!input_type_->ShouldSaveAndRestoreFormControlState()) + return false; + return TextControlElement::ShouldSaveAndRestoreFormControlState(); +} + +FormControlState HTMLInputElement::SaveFormControlState() const { + return input_type_view_->SaveFormControlState(); +} + +void HTMLInputElement::RestoreFormControlState(const FormControlState& state) { + input_type_view_->RestoreFormControlState(state); + state_restored_ = true; +} + +bool HTMLInputElement::CanStartSelection() const { + if (!IsTextField()) + return false; + return TextControlElement::CanStartSelection(); +} + +unsigned HTMLInputElement::selectionStartForBinding( + bool& is_null, + ExceptionState& exception_state) const { + if (!input_type_->SupportsSelectionAPI()) { + is_null = true; + return 0; + } + return TextControlElement::selectionStart(); +} + +unsigned HTMLInputElement::selectionEndForBinding( + bool& is_null, + ExceptionState& exception_state) const { + if (!input_type_->SupportsSelectionAPI()) { + is_null = true; + return 0; + } + return TextControlElement::selectionEnd(); +} + +String HTMLInputElement::selectionDirectionForBinding( + ExceptionState& exception_state) const { + if (!input_type_->SupportsSelectionAPI()) { + return String(); + } + return TextControlElement::selectionDirection(); +} + +void HTMLInputElement::setSelectionStartForBinding( + unsigned start, + bool is_null, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionStart(start); +} + +void HTMLInputElement::setSelectionEndForBinding( + unsigned end, + bool is_null, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionEnd(end); +} + +void HTMLInputElement::setSelectionDirectionForBinding( + const String& direction, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionDirection(direction); +} + +void HTMLInputElement::setSelectionRangeForBinding( + unsigned start, + unsigned end, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionRangeForBinding(start, end); +} + +void HTMLInputElement::setSelectionRangeForBinding( + unsigned start, + unsigned end, + const String& direction, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + TextControlElement::setSelectionRangeForBinding(start, end, direction); +} + +void HTMLInputElement::AccessKeyAction(bool send_mouse_events) { + input_type_view_->AccessKeyAction(send_mouse_events); +} + +bool HTMLInputElement::IsPresentationAttribute( + const QualifiedName& name) const { + // FIXME: Remove type check. + if (name == vspaceAttr || name == hspaceAttr || name == alignAttr || + name == widthAttr || name == heightAttr || + (name == borderAttr && type() == InputTypeNames::image)) + return true; + return TextControlElement::IsPresentationAttribute(name); +} + +void HTMLInputElement::CollectStyleForPresentationAttribute( + const QualifiedName& name, + const AtomicString& value, + MutableCSSPropertyValueSet* 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 (input_type_->ShouldRespectAlignAttribute()) + ApplyAlignmentAttributeToStyle(value, style); + } else if (name == widthAttr) { + if (input_type_->ShouldRespectHeightAndWidthAttributes()) + AddHTMLLengthToStyle(style, CSSPropertyWidth, value); + } else if (name == heightAttr) { + if (input_type_->ShouldRespectHeightAndWidthAttributes()) + AddHTMLLengthToStyle(style, CSSPropertyHeight, value); + } else if (name == borderAttr && + type() == InputTypeNames::image) { // FIXME: Remove type check. + ApplyBorderAttributeToStyle(value, style); + } else { + TextControlElement::CollectStyleForPresentationAttribute(name, value, + style); + } +} + +void HTMLInputElement::ParseAttribute( + const AttributeModificationParams& params) { + DCHECK(input_type_); + DCHECK(input_type_view_); + const QualifiedName& name = params.name; + const AtomicString& value = params.new_value; + + if (name == nameAttr) { + RemoveFromRadioButtonGroup(); + name_ = value; + AddToRadioButtonGroup(); + TextControlElement::ParseAttribute(params); + } else if (name == autocompleteAttr) { + if (DeprecatedEqualIgnoringCase(value, "off")) { + autocomplete_ = kOff; + } else { + if (value.IsEmpty()) + autocomplete_ = kUninitialized; + else + autocomplete_ = kOn; + } + } else if (name == typeAttr) { + UpdateType(); + } else if (name == valueAttr) { + // We only need to setChanged if the form is looking at the default value + // right now. + if (!HasDirtyValue()) { + if (input_type_->GetValueMode() == ValueMode::kValue) + non_attribute_value_ = SanitizeValue(value); + UpdatePlaceholderVisibility(); + SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::FromAttribute(valueAttr)); + } + needs_to_update_view_value_ = true; + SetNeedsValidityCheck(); + input_type_->WarnIfValueIsInvalidAndElementIsVisible(value); + input_type_->InRangeChanged(); + input_type_view_->ValueAttributeChanged(); + } else if (name == checkedAttr) { + // Another radio button in the same group might be checked by state + // restore. We shouldn't call setChecked() even if this has the checked + // attribute. So, delay the setChecked() call until + // finishParsingChildren() is called if parsing is in progress. + if ((!parsing_in_progress_ || + !GetDocument().GetFormController().HasFormStates()) && + !dirty_checkedness_) { + setChecked(!value.IsNull()); + dirty_checkedness_ = false; + } + PseudoStateChanged(CSSSelector::kPseudoDefault); + } else if (name == maxlengthAttr) { + SetNeedsValidityCheck(); + } else if (name == minlengthAttr) { + SetNeedsValidityCheck(); + } else if (name == sizeAttr) { + unsigned size = 0; + if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, size) || + size == 0 || size > 0x7fffffffu) + size = kDefaultSize; + if (size_ != size) { + size_ = size; + if (GetLayoutObject()) + GetLayoutObject() + ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + LayoutInvalidationReason::kAttributeChanged); + } + } else if (name == altAttr) { + input_type_view_->AltAttributeChanged(); + } else if (name == srcAttr) { + input_type_view_->SrcAttributeChanged(); + } else if (name == usemapAttr || name == accesskeyAttr) { + // FIXME: ignore for the moment + } else if (name == onsearchAttr) { + // Search field and slider attributes all just cause updateFromElement to be + // called through style recalcing. + SetAttributeEventListener( + EventTypeNames::search, + CreateAttributeEventListener(this, name, value, EventParameterName())); + } else if (name == incrementalAttr) { + UseCounter::Count(GetDocument(), WebFeature::kIncrementalAttribute); + } else if (name == minAttr) { + input_type_view_->MinOrMaxAttributeChanged(); + input_type_->SanitizeValueInResponseToMinOrMaxAttributeChange(); + input_type_->InRangeChanged(); + SetNeedsValidityCheck(); + UseCounter::Count(GetDocument(), WebFeature::kMinAttribute); + } else if (name == maxAttr) { + input_type_view_->MinOrMaxAttributeChanged(); + input_type_->SanitizeValueInResponseToMinOrMaxAttributeChange(); + input_type_->InRangeChanged(); + SetNeedsValidityCheck(); + UseCounter::Count(GetDocument(), WebFeature::kMaxAttribute); + } else if (name == multipleAttr) { + input_type_view_->MultipleAttributeChanged(); + SetNeedsValidityCheck(); + } else if (name == stepAttr) { + input_type_view_->StepAttributeChanged(); + SetNeedsValidityCheck(); + UseCounter::Count(GetDocument(), WebFeature::kStepAttribute); + } else if (name == patternAttr) { + SetNeedsValidityCheck(); + UseCounter::Count(GetDocument(), WebFeature::kPatternAttribute); + } else if (name == readonlyAttr) { + TextControlElement::ParseAttribute(params); + input_type_view_->ReadonlyAttributeChanged(); + } else if (name == listAttr) { + has_non_empty_list_ = !value.IsEmpty(); + if (has_non_empty_list_) { + ResetListAttributeTargetObserver(); + ListAttributeTargetChanged(); + } + UseCounter::Count(GetDocument(), WebFeature::kListAttribute); + } else if (name == webkitdirectoryAttr) { + TextControlElement::ParseAttribute(params); + UseCounter::Count(GetDocument(), WebFeature::kPrefixedDirectoryAttribute); + } else { + if (name == formactionAttr) + LogUpdateAttributeIfIsolatedWorldAndInDocument("input", params); + TextControlElement::ParseAttribute(params); + } + input_type_view_->AttributeChanged(); +} + +void HTMLInputElement::ParserDidSetAttributes() { + DCHECK(parsing_in_progress_); + InitializeTypeInParsing(); +} + +void HTMLInputElement::FinishParsingChildren() { + parsing_in_progress_ = false; + DCHECK(input_type_); + DCHECK(input_type_view_); + TextControlElement::FinishParsingChildren(); + if (!state_restored_) { + bool checked = hasAttribute(checkedAttr); + if (checked) + setChecked(checked); + dirty_checkedness_ = false; + } +} + +bool HTMLInputElement::LayoutObjectIsNeeded(const ComputedStyle& style) const { + return input_type_->LayoutObjectIsNeeded() && + TextControlElement::LayoutObjectIsNeeded(style); +} + +LayoutObject* HTMLInputElement::CreateLayoutObject(const ComputedStyle& style) { + return input_type_view_->CreateLayoutObject(style); +} + +void HTMLInputElement::AttachLayoutTree(AttachContext& context) { + SyncReattachContext reattach_context(context); + TextControlElement::AttachLayoutTree(context); + if (GetLayoutObject()) { + input_type_->OnAttachWithLayoutObject(); + } + + input_type_view_->StartResourceLoading(); + input_type_->CountUsage(); +} + +void HTMLInputElement::DetachLayoutTree(const AttachContext& context) { + if (GetLayoutObject()) { + input_type_->OnDetachWithLayoutObject(); + } + TextControlElement::DetachLayoutTree(context); + needs_to_update_view_value_ = true; + input_type_view_->ClosePopupView(); +} + +String HTMLInputElement::AltText() const { + // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen + // also heavily discussed by Hixie on bugzilla + // note this is intentionally different to HTMLImageElement::altText() + String alt = FastGetAttribute(altAttr); + // fall back to title attribute + if (alt.IsNull()) + alt = FastGetAttribute(titleAttr); + if (alt.IsNull()) + alt = FastGetAttribute(valueAttr); + if (alt.IsNull()) + alt = GetLocale().QueryString(WebLocalizedString::kInputElementAltText); + return alt; +} + +bool HTMLInputElement::CanBeSuccessfulSubmitButton() const { + return input_type_->CanBeSuccessfulSubmitButton(); +} + +bool HTMLInputElement::IsActivatedSubmit() const { + return is_activated_submit_; +} + +void HTMLInputElement::SetActivatedSubmit(bool flag) { + is_activated_submit_ = flag; +} + +void HTMLInputElement::AppendToFormData(FormData& form_data) { + if (input_type_->IsFormDataAppendable()) + input_type_->AppendToFormData(form_data); +} + +String HTMLInputElement::ResultForDialogSubmit() { + return input_type_->ResultForDialogSubmit(); +} + +void HTMLInputElement::ResetImpl() { + if (input_type_->GetValueMode() == ValueMode::kValue) { + SetNonDirtyValue(DefaultValue()); + SetNeedsValidityCheck(); + } else if (input_type_->GetValueMode() == ValueMode::kFilename) { + SetNonDirtyValue(String()); + SetNeedsValidityCheck(); + } + + setChecked(hasAttribute(checkedAttr)); + dirty_checkedness_ = false; +} + +bool HTMLInputElement::IsTextField() const { + return input_type_->IsTextField(); +} + +bool HTMLInputElement::HasBeenPasswordField() const { + return has_been_password_field_; +} + +void HTMLInputElement::DispatchChangeEventIfNeeded() { + if (isConnected() && input_type_->ShouldSendChangeEventAfterCheckedChanged()) + DispatchChangeEvent(); +} + +void HTMLInputElement::DispatchInputAndChangeEventIfNeeded() { + if (isConnected() && + input_type_->ShouldSendChangeEventAfterCheckedChanged()) { + DispatchInputEvent(); + DispatchChangeEvent(); + } +} + +bool HTMLInputElement::checked() const { + input_type_->ReadingChecked(); + return is_checked_; +} + +void HTMLInputElement::setChecked(bool now_checked, + TextFieldEventBehavior event_behavior) { + dirty_checkedness_ = true; + if (checked() == now_checked) + return; + + is_checked_ = now_checked; + + if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) + scope->UpdateCheckedState(this); + if (LayoutObject* o = GetLayoutObject()) + o->InvalidateIfControlStateChanged(kCheckedControlState); + SetNeedsValidityCheck(); + + // Ideally we'd do this from the layout tree (matching + // LayoutTextView), but it's not possible to do it at the moment + // because of the way the code is structured. + if (GetLayoutObject()) { + if (AXObjectCache* cache = + GetLayoutObject()->GetDocument().ExistingAXObjectCache()) + cache->CheckedStateChanged(this); + } + + // Only send a change event for items in the document (avoid firing during + // parsing) and don't send a change event for a radio button that's getting + // unchecked to match other browsers. DOM is not a useful standard for this + // because it says only to fire change events at "lose focus" time, which is + // definitely wrong in practice for these types of elements. + if (event_behavior == kDispatchInputAndChangeEvent && isConnected() && + input_type_->ShouldSendChangeEventAfterCheckedChanged()) { + DispatchInputEvent(); + } + + PseudoStateChanged(CSSSelector::kPseudoChecked); +} + +void HTMLInputElement::setIndeterminate(bool new_value) { + if (indeterminate() == new_value) + return; + + is_indeterminate_ = new_value; + + PseudoStateChanged(CSSSelector::kPseudoIndeterminate); + + if (LayoutObject* o = GetLayoutObject()) + o->InvalidateIfControlStateChanged(kCheckedControlState); +} + +unsigned HTMLInputElement::size() const { + return size_; +} + +bool HTMLInputElement::SizeShouldIncludeDecoration(int& preferred_size) const { + return input_type_view_->SizeShouldIncludeDecoration(kDefaultSize, + preferred_size); +} + +void HTMLInputElement::CloneNonAttributePropertiesFrom(const Element& source, + CloneChildrenFlag flag) { + const HTMLInputElement& source_element = ToHTMLInputElement(source); + + non_attribute_value_ = source_element.non_attribute_value_; + has_dirty_value_ = source_element.has_dirty_value_; + setChecked(source_element.is_checked_); + dirty_checkedness_ = source_element.dirty_checkedness_; + is_indeterminate_ = source_element.is_indeterminate_; + input_type_->CopyNonAttributeProperties(source_element); + + TextControlElement::CloneNonAttributePropertiesFrom(source, flag); + + needs_to_update_view_value_ = true; + input_type_view_->UpdateView(); +} + +String HTMLInputElement::value() const { + switch (input_type_->GetValueMode()) { + case ValueMode::kFilename: + return input_type_->ValueInFilenameValueMode(); + case ValueMode::kDefault: + return FastGetAttribute(valueAttr); + case ValueMode::kDefaultOn: { + AtomicString value_string = FastGetAttribute(valueAttr); + return value_string.IsNull() ? "on" : value_string; + } + case ValueMode::kValue: + return non_attribute_value_; + } + NOTREACHED(); + return g_empty_string; +} + +String HTMLInputElement::ValueOrDefaultLabel() const { + String value = this->value(); + if (!value.IsNull()) + return value; + return input_type_->DefaultLabel(); +} + +void HTMLInputElement::SetValueForUser(const String& value) { + // Call setValue and make it send a change event. + setValue(value, kDispatchChangeEvent); +} + +void HTMLInputElement::SetSuggestedValue(const String& value) { + if (!input_type_->CanSetSuggestedValue()) + return; + needs_to_update_view_value_ = true; + TextControlElement::SetSuggestedValue(SanitizeValue(value)); + SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue)); + input_type_view_->UpdateView(); +} + +void HTMLInputElement::SetEditingValue(const String& value) { + if (!GetLayoutObject() || !IsTextField()) + return; + SetInnerEditorValue(value); + SubtreeHasChanged(); + + unsigned max = value.length(); + SetSelectionRange(max, max); + DispatchInputEvent(); +} + +void HTMLInputElement::SetInnerEditorValue(const String& value) { + TextControlElement::SetInnerEditorValue(value); + needs_to_update_view_value_ = false; +} + +void HTMLInputElement::setValue(const String& value, + ExceptionState& exception_state, + TextFieldEventBehavior event_behavior) { + // FIXME: Remove type check. + if (type() == InputTypeNames::file && !value.IsEmpty()) { + exception_state.ThrowDOMException(kInvalidStateError, + "This input element accepts a filename, " + "which may only be programmatically set " + "to the empty string."); + return; + } + setValue(value, event_behavior); +} + +void HTMLInputElement::setValue(const String& value, + TextFieldEventBehavior event_behavior, + TextControlSetValueSelection selection) { + input_type_->WarnIfValueIsInvalidAndElementIsVisible(value); + if (!input_type_->CanSetValue(value)) + return; + + // Clear the suggested value. Use the base class version to not trigger a view + // update. + TextControlElement::SetSuggestedValue(String()); + + EventQueueScope scope; + String sanitized_value = SanitizeValue(value); + bool value_changed = sanitized_value != this->value(); + + SetLastChangeWasNotUserEdit(); + needs_to_update_view_value_ = true; + + input_type_->SetValue(sanitized_value, value_changed, event_behavior, + selection); + input_type_view_->DidSetValue(sanitized_value, value_changed); + + if (value_changed) + NotifyFormStateChanged(); +} + +void HTMLInputElement::SetNonAttributeValue(const String& sanitized_value) { + // This is a common code for ValueMode::kValue. + DCHECK_EQ(input_type_->GetValueMode(), ValueMode::kValue); + non_attribute_value_ = sanitized_value; + has_dirty_value_ = true; + SetNeedsValidityCheck(); + input_type_->InRangeChanged(); +} + +void HTMLInputElement::SetNonAttributeValueByUserEdit( + const String& sanitized_value) { + SetValueBeforeFirstUserEditIfNotSet(); + SetNonAttributeValue(sanitized_value); + CheckIfValueWasReverted(sanitized_value); +} + +void HTMLInputElement::SetNonDirtyValue(const String& new_value) { + setValue(new_value); + has_dirty_value_ = false; +} + +bool HTMLInputElement::HasDirtyValue() const { + return has_dirty_value_; +} + +void HTMLInputElement::UpdateView() { + input_type_view_->UpdateView(); +} + +double HTMLInputElement::valueAsDate(bool& is_null) const { + double date = input_type_->ValueAsDate(); + is_null = !std::isfinite(date); + return date; +} + +void HTMLInputElement::setValueAsDate(double value, + bool is_null, + ExceptionState& exception_state) { + input_type_->SetValueAsDate(value, exception_state); +} + +double HTMLInputElement::valueAsNumber() const { + return input_type_->ValueAsDouble(); +} + +void HTMLInputElement::setValueAsNumber(double new_value, + ExceptionState& exception_state, + TextFieldEventBehavior event_behavior) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#dom-input-valueasnumber + // On setting, if the new value is infinite, then throw a TypeError exception. + if (std::isinf(new_value)) { + exception_state.ThrowTypeError( + ExceptionMessages::NotAFiniteNumber(new_value)); + return; + } + input_type_->SetValueAsDouble(new_value, event_behavior, exception_state); +} + +void HTMLInputElement::SetValueFromRenderer(const String& value) { + // File upload controls will never use this. + DCHECK_NE(type(), InputTypeNames::file); + + // Clear the suggested value. Use the base class version to not trigger a view + // update. + TextControlElement::SetSuggestedValue(String()); + + // Renderer and our event handler are responsible for sanitizing values. + DCHECK(value == input_type_->SanitizeUserInputValue(value) || + input_type_->SanitizeUserInputValue(value).IsEmpty()); + + DCHECK(!value.IsNull()); + SetValueBeforeFirstUserEditIfNotSet(); + non_attribute_value_ = value; + has_dirty_value_ = true; + needs_to_update_view_value_ = false; + CheckIfValueWasReverted(value); + + // Input event is fired by the Node::defaultEventHandler for editable + // controls. + if (!IsTextField()) + DispatchInputEvent(); + NotifyFormStateChanged(); + + SetNeedsValidityCheck(); + + // Clear autofill flag (and yellow background) on user edit. + SetAutofilled(false); +} + +EventDispatchHandlingState* HTMLInputElement::PreDispatchEventHandler( + Event* event) { + if (event->type() == EventTypeNames::textInput && + input_type_view_->ShouldSubmitImplicitly(event)) { + event->stopPropagation(); + return nullptr; + } + if (event->type() != EventTypeNames::click) + return nullptr; + if (!event->IsMouseEvent() || + ToMouseEvent(event)->button() != + static_cast<short>(WebPointerProperties::Button::kLeft)) + return nullptr; + return input_type_view_->WillDispatchClick(); +} + +void HTMLInputElement::PostDispatchEventHandler( + Event* event, + EventDispatchHandlingState* state) { + if (!state) + return; + input_type_view_->DidDispatchClick(event, + *static_cast<ClickHandlingState*>(state)); +} + +void HTMLInputElement::DefaultEventHandler(Event* evt) { + if (evt->IsMouseEvent() && evt->type() == EventTypeNames::click && + ToMouseEvent(evt)->button() == + static_cast<short>(WebPointerProperties::Button::kLeft)) { + input_type_view_->HandleClickEvent(ToMouseEvent(evt)); + if (evt->DefaultHandled()) + return; + } + + if (evt->IsKeyboardEvent() && evt->type() == EventTypeNames::keydown) { + input_type_view_->HandleKeydownEvent(ToKeyboardEvent(evt)); + if (evt->DefaultHandled()) + return; + } + + // Call the base event handler before any of our own event handling for almost + // all events in text fields. Makes editing keyboard handling take precedence + // over the keydown and keypress handling in this function. + bool call_base_class_early = + IsTextField() && (evt->type() == EventTypeNames::keydown || + evt->type() == EventTypeNames::keypress); + if (call_base_class_early) { + TextControlElement::DefaultEventHandler(evt); + if (evt->DefaultHandled()) + return; + } + + // DOMActivate events cause the input to be "activated" - in the case of image + // and submit inputs, this means actually submitting the form. For reset + // inputs, the form is reset. These events are sent when the user clicks on + // the element, or presses enter while it is the active element. JavaScript + // code wishing to activate the element must dispatch a DOMActivate event - a + // click event will not do the job. + if (evt->type() == EventTypeNames::DOMActivate) { + input_type_view_->HandleDOMActivateEvent(evt); + if (evt->DefaultHandled()) + return; + } + + // Use key press event here since sending simulated mouse events + // on key down blocks the proper sending of the key press event. + if (evt->IsKeyboardEvent() && evt->type() == EventTypeNames::keypress) { + input_type_view_->HandleKeypressEvent(ToKeyboardEvent(evt)); + if (evt->DefaultHandled()) + return; + } + + if (evt->IsKeyboardEvent() && evt->type() == EventTypeNames::keyup) { + input_type_view_->HandleKeyupEvent(ToKeyboardEvent(evt)); + if (evt->DefaultHandled()) + return; + } + + if (input_type_view_->ShouldSubmitImplicitly(evt)) { + // FIXME: Remove type check. + if (type() == InputTypeNames::search) { + GetDocument() + .GetTaskRunner(TaskType::kUserInteraction) + ->PostTask(FROM_HERE, WTF::Bind(&HTMLInputElement::OnSearch, + WrapPersistent(this))); + } + // Form submission finishes editing, just as loss of focus does. + // If there was a change, send the event now. + DispatchFormControlChangeEvent(); + + HTMLFormElement* form_for_submission = + input_type_view_->FormForSubmission(); + // Form may never have been present, or may have been destroyed by code + // responding to the change event. + if (form_for_submission) { + form_for_submission->SubmitImplicitly(evt, + CanTriggerImplicitSubmission()); + } + evt->SetDefaultHandled(); + return; + } + + if (evt->IsBeforeTextInsertedEvent()) { + input_type_view_->HandleBeforeTextInsertedEvent( + static_cast<BeforeTextInsertedEvent*>(evt)); + } + + if (evt->IsMouseEvent() && evt->type() == EventTypeNames::mousedown) { + input_type_view_->HandleMouseDownEvent(ToMouseEvent(evt)); + if (evt->DefaultHandled()) + return; + } + + input_type_view_->ForwardEvent(evt); + + if (!call_base_class_early && !evt->DefaultHandled()) + TextControlElement::DefaultEventHandler(evt); +} + +void HTMLInputElement::CreateShadowSubtree() { + input_type_view_->CreateShadowSubtree(); +} + +bool HTMLInputElement::HasActivationBehavior() const { + return true; +} + +bool HTMLInputElement::WillRespondToMouseClickEvents() { + // FIXME: Consider implementing willRespondToMouseClickEvents() in InputType + // if more accurate results are necessary. + if (!IsDisabledFormControl()) + return true; + + return TextControlElement::WillRespondToMouseClickEvents(); +} + +bool HTMLInputElement::IsURLAttribute(const Attribute& attribute) const { + return attribute.GetName() == srcAttr || + attribute.GetName() == formactionAttr || + TextControlElement::IsURLAttribute(attribute); +} + +bool HTMLInputElement::HasLegalLinkAttribute(const QualifiedName& name) const { + return input_type_->HasLegalLinkAttribute(name) || + TextControlElement::HasLegalLinkAttribute(name); +} + +const QualifiedName& HTMLInputElement::SubResourceAttributeName() const { + return input_type_->SubResourceAttributeName(); +} + +const AtomicString& HTMLInputElement::DefaultValue() const { + return FastGetAttribute(valueAttr); +} + +static inline bool IsRFC2616TokenCharacter(UChar ch) { + return IsASCII(ch) && ch > ' ' && ch != '"' && ch != '(' && ch != ')' && + ch != ',' && ch != '/' && (ch < ':' || ch > '@') && + (ch < '[' || ch > ']') && ch != '{' && ch != '}' && ch != 0x7f; +} + +static bool IsValidMIMEType(const String& type) { + size_t slash_position = type.find('/'); + if (slash_position == kNotFound || !slash_position || + slash_position == type.length() - 1) + return false; + for (size_t i = 0; i < type.length(); ++i) { + if (!IsRFC2616TokenCharacter(type[i]) && i != slash_position) + 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& accept_string, + bool (*predicate)(const String&)) { + Vector<String> types; + if (accept_string.IsEmpty()) + return types; + + Vector<String> split_types; + accept_string.Split(',', false, split_types); + for (const String& split_type : split_types) { + String trimmed_type = StripLeadingAndTrailingHTMLSpaces(split_type); + if (trimmed_type.IsEmpty()) + continue; + if (!predicate(trimmed_type)) + continue; + types.push_back(trimmed_type.DeprecatedLower()); + } + + return types; +} + +Vector<String> HTMLInputElement::AcceptMIMETypes() const { + return ParseAcceptAttribute(FastGetAttribute(acceptAttr), IsValidMIMEType); +} + +Vector<String> HTMLInputElement::AcceptFileExtensions() const { + return ParseAcceptAttribute(FastGetAttribute(acceptAttr), + IsValidFileExtension); +} + +const AtomicString& HTMLInputElement::Alt() const { + return FastGetAttribute(altAttr); +} + +bool HTMLInputElement::Multiple() const { + return FastHasAttribute(multipleAttr); +} + +void HTMLInputElement::setSize(unsigned size, ExceptionState& exception_state) { + if (size == 0) { + exception_state.ThrowDOMException( + kIndexSizeError, "The value provided is 0, which is an invalid size."); + } else { + SetUnsignedIntegralAttribute(sizeAttr, size ? size : kDefaultSize, + kDefaultSize); + } +} + +KURL HTMLInputElement::Src() const { + return GetDocument().CompleteURL(FastGetAttribute(srcAttr)); +} + +FileList* HTMLInputElement::files() const { + return input_type_->Files(); +} + +void HTMLInputElement::setFiles(FileList* files) { + input_type_->SetFiles(files); +} + +bool HTMLInputElement::ReceiveDroppedFiles(const DragData* drag_data) { + return input_type_->ReceiveDroppedFiles(drag_data); +} + +String HTMLInputElement::DroppedFileSystemId() { + return input_type_->DroppedFileSystemId(); +} + +bool HTMLInputElement::CanReceiveDroppedFiles() const { + return can_receive_dropped_files_; +} + +void HTMLInputElement::SetCanReceiveDroppedFiles( + bool can_receive_dropped_files) { + if (!!can_receive_dropped_files_ == can_receive_dropped_files) + return; + can_receive_dropped_files_ = can_receive_dropped_files; + if (GetLayoutObject()) + GetLayoutObject()->UpdateFromElement(); +} + +String HTMLInputElement::SanitizeValue(const String& proposed_value) const { + return input_type_->SanitizeValue(proposed_value); +} + +String HTMLInputElement::LocalizeValue(const String& proposed_value) const { + if (proposed_value.IsNull()) + return proposed_value; + return input_type_->LocalizeValue(proposed_value); +} + +bool HTMLInputElement::IsInRange() const { + return willValidate() && input_type_->IsInRange(value()); +} + +bool HTMLInputElement::IsOutOfRange() const { + return willValidate() && input_type_->IsOutOfRange(value()); +} + +bool HTMLInputElement::IsRequiredFormControl() const { + return input_type_->SupportsRequired() && IsRequired(); +} + +bool HTMLInputElement::MatchesReadOnlyPseudoClass() const { + return input_type_->SupportsReadOnly() && IsReadOnly(); +} + +bool HTMLInputElement::MatchesReadWritePseudoClass() const { + return input_type_->SupportsReadOnly() && !IsReadOnly(); +} + +void HTMLInputElement::OnSearch() { + input_type_->DispatchSearchEvent(); +} + +void HTMLInputElement::UpdateClearButtonVisibility() { + input_type_view_->UpdateClearButtonVisibility(); +} + +void HTMLInputElement::WillChangeForm() { + if (input_type_) + RemoveFromRadioButtonGroup(); + TextControlElement::WillChangeForm(); +} + +void HTMLInputElement::DidChangeForm() { + TextControlElement::DidChangeForm(); + if (input_type_) + AddToRadioButtonGroup(); +} + +Node::InsertionNotificationRequest HTMLInputElement::InsertedInto( + ContainerNode* insertion_point) { + TextControlElement::InsertedInto(insertion_point); + if (insertion_point->isConnected() && !Form()) + AddToRadioButtonGroup(); + ResetListAttributeTargetObserver(); + LogAddElementIfIsolatedWorldAndInDocument("input", typeAttr, formactionAttr); + return kInsertionShouldCallDidNotifySubtreeInsertions; +} + +void HTMLInputElement::RemovedFrom(ContainerNode* insertion_point) { + input_type_view_->ClosePopupView(); + if (insertion_point->isConnected() && !Form()) + RemoveFromRadioButtonGroup(); + TextControlElement::RemovedFrom(insertion_point); + DCHECK(!isConnected()); + ResetListAttributeTargetObserver(); +} + +void HTMLInputElement::DidMoveToNewDocument(Document& old_document) { + if (ImageLoader()) + ImageLoader()->ElementDidMoveToNewDocument(); + + // FIXME: Remove type check. + if (type() == InputTypeNames::radio) + GetTreeScope().GetRadioButtonGroupScope().RemoveButton(this); + + TextControlElement::DidMoveToNewDocument(old_document); +} + +bool HTMLInputElement::RecalcWillValidate() const { + return input_type_->SupportsValidation() && + TextControlElement::RecalcWillValidate(); +} + +void HTMLInputElement::RequiredAttributeChanged() { + TextControlElement::RequiredAttributeChanged(); + if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) + scope->RequiredAttributeChanged(this); + input_type_view_->RequiredAttributeChanged(); +} + +void HTMLInputElement::DisabledAttributeChanged() { + TextControlElement::DisabledAttributeChanged(); + input_type_view_->DisabledAttributeChanged(); +} + +void HTMLInputElement::SelectColorInColorChooser(const Color& color) { + if (ColorChooserClient* client = input_type_->GetColorChooserClient()) + client->DidChooseColor(color); +} + +void HTMLInputElement::EndColorChooser() { + if (ColorChooserClient* client = input_type_->GetColorChooserClient()) + client->DidEndChooser(); +} + +HTMLElement* HTMLInputElement::list() const { + return DataList(); +} + +HTMLDataListElement* HTMLInputElement::DataList() const { + if (!has_non_empty_list_) + return nullptr; + + if (!input_type_->ShouldRespectListAttribute()) + return nullptr; + + return ToHTMLDataListElementOrNull( + GetTreeScope().getElementById(FastGetAttribute(listAttr))); +} + +bool HTMLInputElement::HasValidDataListOptions() const { + HTMLDataListElement* data_list = DataList(); + if (!data_list) + return false; + HTMLDataListOptionsCollection* options = data_list->options(); + for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); ++i) { + if (!option->value().IsEmpty() && !option->IsDisabledFormControl() && + IsValidValue(option->value())) + return true; + } + return false; +} + +HeapVector<Member<HTMLOptionElement>> +HTMLInputElement::FilteredDataListOptions() const { + HeapVector<Member<HTMLOptionElement>> filtered; + HTMLDataListElement* data_list = DataList(); + if (!data_list) + return filtered; + + String value = InnerEditorValue(); + if (Multiple() && type() == InputTypeNames::email) { + Vector<String> emails; + value.Split(',', true, emails); + if (!emails.IsEmpty()) + value = emails.back().StripWhiteSpace(); + } + + HTMLDataListOptionsCollection* options = data_list->options(); + filtered.ReserveCapacity(options->length()); + value = value.FoldCase(); + for (unsigned i = 0; i < options->length(); ++i) { + HTMLOptionElement* option = options->Item(i); + DCHECK(option); + if (!value.IsEmpty()) { + // Firefox shows OPTIONs with matched labels, Edge shows OPTIONs + // with matches values. We show both. + if (option->value().FoldCase().Find(value) == kNotFound && + option->label().FoldCase().Find(value) == kNotFound) + continue; + } + // TODO(tkent): Should allow invalid strings. crbug.com/607097. + if (option->value().IsEmpty() || option->IsDisabledFormControl() || + !IsValidValue(option->value())) + continue; + filtered.push_back(option); + } + return filtered; +} + +void HTMLInputElement::SetListAttributeTargetObserver( + ListAttributeTargetObserver* new_observer) { + if (list_attribute_target_observer_) + list_attribute_target_observer_->Unregister(); + list_attribute_target_observer_ = new_observer; +} + +void HTMLInputElement::ResetListAttributeTargetObserver() { + const AtomicString& value = FastGetAttribute(listAttr); + if (!value.IsNull() && isConnected()) { + SetListAttributeTargetObserver( + ListAttributeTargetObserver::Create(value, this)); + } else { + SetListAttributeTargetObserver(nullptr); + } +} + +void HTMLInputElement::ListAttributeTargetChanged() { + input_type_view_->ListAttributeTargetChanged(); +} + +bool HTMLInputElement::IsSteppable() const { + return input_type_->IsSteppable(); +} + +bool HTMLInputElement::IsTextButton() const { + return input_type_->IsTextButton(); +} + +bool HTMLInputElement::IsEnumeratable() const { + return input_type_->IsEnumeratable(); +} + +bool HTMLInputElement::SupportLabels() const { + return input_type_->IsInteractiveContent(); +} + +bool HTMLInputElement::MatchesDefaultPseudoClass() const { + return input_type_->MatchesDefaultPseudoClass(); +} + +bool HTMLInputElement::ShouldAppearChecked() const { + return checked() && input_type_->IsCheckable(); +} + +void HTMLInputElement::SetPlaceholderVisibility(bool visible) { + is_placeholder_visible_ = visible; +} + +bool HTMLInputElement::SupportsPlaceholder() const { + return input_type_->SupportsPlaceholder(); +} + +void HTMLInputElement::UpdatePlaceholderText() { + return input_type_view_->UpdatePlaceholderText(); +} + +String HTMLInputElement::GetPlaceholderValue() const { + return !SuggestedValue().IsEmpty() ? SuggestedValue() : StrippedPlaceholder(); +} + +String HTMLInputElement::DefaultToolTip() const { + return input_type_->DefaultToolTip(*input_type_view_); +} + +bool HTMLInputElement::ShouldAppearIndeterminate() const { + return input_type_->ShouldAppearIndeterminate(); +} + +bool HTMLInputElement::IsInRequiredRadioButtonGroup() { + // TODO(tkent): Remove type check. + DCHECK_EQ(type(), InputTypeNames::radio); + if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) + return scope->IsInRequiredGroup(this); + return false; +} + +HTMLInputElement* HTMLInputElement::CheckedRadioButtonForGroup() { + if (checked()) + return this; + if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) + return scope->CheckedButtonForGroup(GetName()); + return nullptr; +} + +RadioButtonGroupScope* HTMLInputElement::GetRadioButtonGroupScope() const { + // FIXME: Remove type check. + if (type() != InputTypeNames::radio) + return nullptr; + if (HTMLFormElement* form_element = Form()) + return &form_element->GetRadioButtonGroupScope(); + if (isConnected()) + return &GetTreeScope().GetRadioButtonGroupScope(); + return nullptr; +} + +unsigned HTMLInputElement::SizeOfRadioGroup() const { + RadioButtonGroupScope* scope = GetRadioButtonGroupScope(); + if (!scope) + return 0; + return scope->GroupSizeFor(this); +} + +inline void HTMLInputElement::AddToRadioButtonGroup() { + if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) + scope->AddButton(this); +} + +inline void HTMLInputElement::RemoveFromRadioButtonGroup() { + if (RadioButtonGroupScope* scope = GetRadioButtonGroupScope()) + scope->RemoveButton(this); +} + +unsigned HTMLInputElement::height() const { + return input_type_->Height(); +} + +unsigned HTMLInputElement::width() const { + return input_type_->Width(); +} + +void HTMLInputElement::setHeight(unsigned height) { + SetUnsignedIntegralAttribute(heightAttr, height); +} + +void HTMLInputElement::setWidth(unsigned width) { + SetUnsignedIntegralAttribute(widthAttr, width); +} + +ListAttributeTargetObserver* ListAttributeTargetObserver::Create( + const AtomicString& id, + HTMLInputElement* element) { + return new ListAttributeTargetObserver(id, element); +} + +ListAttributeTargetObserver::ListAttributeTargetObserver( + const AtomicString& id, + HTMLInputElement* element) + : IdTargetObserver(element->GetTreeScope().GetIdTargetObserverRegistry(), + id), + element_(element) {} + +void ListAttributeTargetObserver::Trace(blink::Visitor* visitor) { + visitor->Trace(element_); + IdTargetObserver::Trace(visitor); +} + +void ListAttributeTargetObserver::IdTargetChanged() { + element_->ListAttributeTargetChanged(); +} + +void HTMLInputElement::setRangeText(const String& replacement, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + + TextControlElement::setRangeText(replacement, exception_state); +} + +void HTMLInputElement::setRangeText(const String& replacement, + unsigned start, + unsigned end, + const String& selection_mode, + ExceptionState& exception_state) { + if (!input_type_->SupportsSelectionAPI()) { + exception_state.ThrowDOMException(kInvalidStateError, + "The input element's type ('" + + input_type_->FormControlType() + + "') does not support selection."); + return; + } + + TextControlElement::setRangeText(replacement, start, end, selection_mode, + exception_state); +} + +bool HTMLInputElement::SetupDateTimeChooserParameters( + DateTimeChooserParameters& parameters) { + if (!GetDocument().View()) + return false; + + parameters.type = type(); + parameters.minimum = Minimum(); + parameters.maximum = Maximum(); + parameters.required = IsRequired(); + if (!RuntimeEnabledFeatures::LangAttributeAwareFormControlUIEnabled()) { + parameters.locale = DefaultLanguage(); + } else { + AtomicString computed_locale = ComputeInheritedLanguage(); + parameters.locale = + computed_locale.IsEmpty() ? DefaultLanguage() : computed_locale; + } + + StepRange step_range = CreateStepRange(kRejectAny); + if (step_range.HasStep()) { + parameters.step = step_range.Step().ToDouble(); + parameters.step_base = step_range.StepBase().ToDouble(); + } else { + parameters.step = 1.0; + parameters.step_base = 0; + } + + parameters.anchor_rect_in_screen = + GetDocument().View()->ContentsToScreen(PixelSnappedBoundingBox()); + parameters.double_value = input_type_->ValueAsDouble(); + parameters.is_anchor_element_rtl = + input_type_view_->ComputedTextDirection() == TextDirection::kRtl; + if (HTMLDataListElement* data_list = DataList()) { + HTMLDataListOptionsCollection* options = data_list->options(); + for (unsigned i = 0; HTMLOptionElement* option = options->Item(i); ++i) { + if (option->value().IsEmpty() || option->IsDisabledFormControl() || + !IsValidValue(option->value())) + continue; + DateTimeSuggestion suggestion; + suggestion.value = + input_type_->ParseToNumber(option->value(), Decimal::Nan()) + .ToDouble(); + if (std::isnan(suggestion.value)) + continue; + suggestion.localized_value = LocalizeValue(option->value()); + suggestion.label = + option->value() == option->label() ? String() : option->label(); + parameters.suggestions.push_back(suggestion); + } + } + return true; +} + +bool HTMLInputElement::SupportsInputModeAttribute() const { + return input_type_->SupportsInputModeAttribute(); +} + +void HTMLInputElement::SetShouldRevealPassword(bool value) { + if (!!should_reveal_password_ == value) + return; + should_reveal_password_ = value; + LazyReattachIfAttached(); +} + +bool HTMLInputElement::IsInteractiveContent() const { + return input_type_->IsInteractiveContent(); +} + +bool HTMLInputElement::SupportsAutofocus() const { + return input_type_->IsInteractiveContent(); +} + +scoped_refptr<ComputedStyle> HTMLInputElement::CustomStyleForLayoutObject() { + return input_type_view_->CustomStyleForLayoutObject( + OriginalStyleForLayoutObject()); +} + +void HTMLInputElement::DidNotifySubtreeInsertionsToDocument() { + ListAttributeTargetChanged(); +} + +AXObject* HTMLInputElement::PopupRootAXObject() { + return input_type_view_->PopupRootAXObject(); +} + +void HTMLInputElement::EnsureFallbackContent() { + input_type_view_->EnsureFallbackContent(); +} + +void HTMLInputElement::EnsurePrimaryContent() { + input_type_view_->EnsurePrimaryContent(); +} + +bool HTMLInputElement::HasFallbackContent() const { + return input_type_view_->HasFallbackContent(); +} + +void HTMLInputElement::SetFilesFromPaths(const Vector<String>& paths) { + return input_type_->SetFilesFromPaths(paths); +} + +void HTMLInputElement::ChildrenChanged(const ChildrenChange& change) { + // Some input types only need shadow roots to hide any children that may + // have been appended by script. For such types, shadow roots are lazily + // created when children are added for the first time. + EnsureUserAgentShadowRoot(); + ContainerNode::ChildrenChanged(change); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h new file mode 100644 index 00000000000..06effcc35eb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.h @@ -0,0 +1,441 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved. + * 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ + +#include "base/gtest_prod_util.h" +#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/dom/create_element_flags.h" +#include "third_party/blink/renderer/core/html/forms/file_chooser.h" +#include "third_party/blink/renderer/core/html/forms/step_range.h" +#include "third_party/blink/renderer/core/html/forms/text_control_element.h" + +namespace blink { + +class AXObject; +class DragData; +class ExceptionState; +class FileList; +class HTMLDataListElement; +class HTMLImageLoader; +class InputType; +class InputTypeView; +class KURL; +class ListAttributeTargetObserver; +class RadioButtonGroupScope; +struct DateTimeChooserParameters; + +class CORE_EXPORT HTMLInputElement + : public TextControlElement, + public ActiveScriptWrappable<HTMLInputElement> { + DEFINE_WRAPPERTYPEINFO(); + USING_GARBAGE_COLLECTED_MIXIN(HTMLInputElement); + + public: + static HTMLInputElement* Create(Document&, const CreateElementFlags); + ~HTMLInputElement() override; + void Trace(blink::Visitor*) override; + + bool HasPendingActivity() const final; + + DEFINE_ATTRIBUTE_EVENT_LISTENER(webkitspeechchange); + + bool ShouldAutocomplete() const final; + + // For ValidityState + bool HasBadInput() const final; + bool PatternMismatch() const final; + bool RangeUnderflow() const final; + bool RangeOverflow() const final; + bool StepMismatch() const final; + bool TooLong() const final; + bool TooShort() const final; + bool TypeMismatch() const final; + bool ValueMissing() const final; + String validationMessage() const final; + String ValidationSubMessage() const final; + + // Returns the minimum value for type=date, number, or range. Don't call this + // for other types. + double Minimum() const; + // Returns the maximum value for type=date, number, or range. Don't call this + // for other types. This always returns a value which is >= minimum(). + double Maximum() const; + // Sets the "allowed value step" defined in the HTML spec to the specified + // double pointer. Returns false if there is no "allowed value step." + bool GetAllowedValueStep(Decimal*) const; + StepRange CreateStepRange(AnyStepHandling) const; + + Decimal FindClosestTickMarkValue(const Decimal&); + + // Implementations of HTMLInputElement::stepUp() and stepDown(). + void stepUp(int, ExceptionState&); + void stepDown(int, ExceptionState&); + // stepUp()/stepDown() for user-interaction. + bool IsSteppable() const; + + // Returns true if the type is button, reset, or submit. + bool IsTextButton() const; + // Returns true if the type is email, number, password, search, tel, text, + // or url. + bool IsTextField() const; + // Do not add type check predicates for concrete input types; e.g. isImage, + // isRadio, isFile. If you want to check the input type, you may use + // |input->type() == InputTypeNames::image|, etc. + + // Returns whether this field is or has ever been a password field so that + // its value can be protected from memorization by autofill or keyboards. + bool HasBeenPasswordField() const; + + bool checked() const; + void setChecked(bool, TextFieldEventBehavior = kDispatchNoEvent); + void DispatchChangeEventIfNeeded(); + void DispatchInputAndChangeEventIfNeeded(); + + // 'indeterminate' is a state independent of the checked state that causes the + // control to draw in a way that hides the actual state. + bool indeterminate() const { return is_indeterminate_; } + void setIndeterminate(bool); + // shouldAppearChecked is used by the layout tree/CSS while checked() is used + // by JS to determine checked state + bool ShouldAppearChecked() const; + bool ShouldAppearIndeterminate() const override; + + unsigned size() const; + bool SizeShouldIncludeDecoration(int& preferred_size) const; + + void setType(const AtomicString&); + + String value() const override; + void setValue(const String&, + ExceptionState&, + TextFieldEventBehavior = kDispatchNoEvent); + void setValue(const String&, + TextFieldEventBehavior = kDispatchNoEvent, + TextControlSetValueSelection = + TextControlSetValueSelection::kSetSelectionToEnd) override; + void SetValueForUser(const String&); + // Update the value, and clear hasDirtyValue() flag. + void SetNonDirtyValue(const String&); + // Checks if the specified string would be a valid value. + // We should not call this for types with no string value such as CHECKBOX and + // RADIO. + bool IsValidValue(const String&) const; + bool HasDirtyValue() const; + + String SanitizeValue(const String&) const; + + String LocalizeValue(const String&) const; + + void SetSuggestedValue(const String& value) override; + + void SetEditingValue(const String&); + + double valueAsDate(bool& is_null) const; + void setValueAsDate(double, bool is_null, ExceptionState&); + + double valueAsNumber() const; + void setValueAsNumber(double, + ExceptionState&, + TextFieldEventBehavior = kDispatchNoEvent); + + String ValueOrDefaultLabel() const; + + // This function dispatches 'input' event for non-textfield types. Callers + // need to handle any DOM structure changes by event handlers, or need to + // delay the 'input' event with EventQueueScope. + void SetValueFromRenderer(const String&); + + unsigned selectionStartForBinding(bool&, ExceptionState&) const; + unsigned selectionEndForBinding(bool&, ExceptionState&) const; + String selectionDirectionForBinding(ExceptionState&) const; + void setSelectionStartForBinding(unsigned, bool is_null, ExceptionState&); + void setSelectionEndForBinding(unsigned, bool is_null, ExceptionState&); + void setSelectionDirectionForBinding(const String&, ExceptionState&); + void setSelectionRangeForBinding(unsigned start, + unsigned end, + ExceptionState&); + void setSelectionRangeForBinding(unsigned start, + unsigned end, + const String& direction, + ExceptionState&); + + bool LayoutObjectIsNeeded(const ComputedStyle&) const final; + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + void DetachLayoutTree(const AttachContext& = AttachContext()) final; + void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus, + const FocusOptions&) final; + + // FIXME: For isActivatedSubmit and setActivatedSubmit, we should use the + // NVI-idiom here by making it private virtual in all classes and expose a + // public method in HTMLFormControlElement to call + // the private virtual method. + bool IsActivatedSubmit() const final; + void SetActivatedSubmit(bool flag) final; + + String AltText() const final; + + const AtomicString& DefaultValue() const; + + Vector<String> AcceptMIMETypes() const; + Vector<String> AcceptFileExtensions() const; + const AtomicString& Alt() const; + + void setSize(unsigned, ExceptionState&); + + KURL Src() const; + bool Multiple() const; + + FileList* files() const; + void setFiles(FileList*); + + void SetFilesFromPaths(const Vector<String>&); + + // Returns true if the given DragData has more than one dropped files. + bool ReceiveDroppedFiles(const DragData*); + + String DroppedFileSystemId(); + + // These functions are used for laying out the input active during a + // drag-and-drop operation. + bool CanReceiveDroppedFiles() const; + void SetCanReceiveDroppedFiles(bool); + + void OnSearch(); + + void UpdateClearButtonVisibility(); + + bool WillRespondToMouseClickEvents() override; + + HTMLElement* list() const; + HTMLDataListElement* DataList() const; + bool HasValidDataListOptions() const; + void ListAttributeTargetChanged(); + // Associated <datalist> options which match to the current INPUT value. + HeapVector<Member<HTMLOptionElement>> FilteredDataListOptions() const; + + HTMLInputElement* CheckedRadioButtonForGroup(); + bool IsInRequiredRadioButtonGroup(); + + // Functions for InputType classes. + void SetNonAttributeValue(const String&); + void SetNonAttributeValueByUserEdit(const String&); + void UpdateView(); + bool NeedsToUpdateViewValue() const { return needs_to_update_view_value_; } + void SetInnerEditorValue(const String&) override; + + // For test purposes. + void SelectColorInColorChooser(const Color&); + void EndColorChooser(); + + String DefaultToolTip() const override; + + unsigned height() const; + unsigned width() const; + void setHeight(unsigned); + void setWidth(unsigned); + + void blur() final; + void DefaultBlur(); + + const AtomicString& GetName() const final; + + void EndEditing(); + + static Vector<FileChooserFileInfo> FilesFromFileInputFormControlState( + const FormControlState&); + + bool MatchesReadOnlyPseudoClass() const final; + bool MatchesReadWritePseudoClass() const final; + void setRangeText(const String& replacement, ExceptionState&) final; + void setRangeText(const String& replacement, + unsigned start, + unsigned end, + const String& selection_mode, + ExceptionState&) final; + + HTMLImageLoader* ImageLoader() const { return image_loader_.Get(); } + HTMLImageLoader& EnsureImageLoader(); + + bool SetupDateTimeChooserParameters(DateTimeChooserParameters&); + + bool SupportsInputModeAttribute() const; + + void SetShouldRevealPassword(bool value); + bool ShouldRevealPassword() const { return should_reveal_password_; } + AXObject* PopupRootAXObject(); + void DidNotifySubtreeInsertionsToDocument() override; + + virtual void EnsureFallbackContent(); + virtual void EnsurePrimaryContent(); + bool HasFallbackContent() const; + + bool IsPlaceholderVisible() const override { return is_placeholder_visible_; } + void SetPlaceholderVisibility(bool) override; + + unsigned SizeOfRadioGroup() const; + + bool SupportsPlaceholder() const final; + String GetPlaceholderValue() const final; + + void ChildrenChanged(const ChildrenChange&) override; + + protected: + HTMLInputElement(Document&, const CreateElementFlags); + + void DefaultEventHandler(Event*) override; + void CreateShadowSubtree(); + + private: + enum AutoCompleteSetting { kUninitialized, kOn, kOff }; + + void WillChangeForm() final; + void DidChangeForm() final; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void RemovedFrom(ContainerNode*) final; + void DidMoveToNewDocument(Document& old_document) final; + bool HasActivationBehavior() const override; + + bool HasCustomFocusLogic() const final; + bool IsKeyboardFocusable() const final; + bool ShouldShowFocusRingOnMouseFocus() const final; + bool IsEnumeratable() const final; + bool IsInteractiveContent() const final; + bool SupportLabels() const final; + bool MatchesDefaultPseudoClass() const override; + + bool IsTextControl() const final { return IsTextField(); } + + bool CanTriggerImplicitSubmission() const final { return IsTextField(); } + + const AtomicString& FormControlType() const final; + + bool ShouldSaveAndRestoreFormControlState() const final; + FormControlState SaveFormControlState() const final; + void RestoreFormControlState(const FormControlState&) final; + + bool CanStartSelection() const final; + + void AccessKeyAction(bool send_mouse_events) final; + + void ParseAttribute(const AttributeModificationParams&) override; + bool IsPresentationAttribute(const QualifiedName&) const final; + void CollectStyleForPresentationAttribute(const QualifiedName&, + const AtomicString&, + MutableCSSPropertyValueSet*) final; + void FinishParsingChildren() final; + void ParserDidSetAttributes() final; + + void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) final; + + void AttachLayoutTree(AttachContext&) final; + + void AppendToFormData(FormData&) final; + String ResultForDialogSubmit() final; + + bool CanBeSuccessfulSubmitButton() const final; + + void ResetImpl() final; + bool SupportsAutofocus() const final; + + EventDispatchHandlingState* PreDispatchEventHandler(Event*) final; + void PostDispatchEventHandler(Event*, EventDispatchHandlingState*) final; + + bool IsURLAttribute(const Attribute&) const final; + bool HasLegalLinkAttribute(const QualifiedName&) const final; + const QualifiedName& SubResourceAttributeName() const final; + bool IsInRange() const final; + bool IsOutOfRange() const final; + + bool TooLong(const String&, NeedsToCheckDirtyFlag) const; + bool TooShort(const String&, NeedsToCheckDirtyFlag) const; + + void UpdatePlaceholderText() final; + bool IsEmptyValue() const final { return InnerEditorValue().IsEmpty(); } + void HandleBlurEvent() final; + void DispatchFocusInEvent(const AtomicString& event_type, + Element* old_focused_element, + WebFocusType, + InputDeviceCapabilities* source_capabilities) final; + + bool IsOptionalFormControl() const final { return !IsRequiredFormControl(); } + bool IsRequiredFormControl() const final; + bool RecalcWillValidate() const final; + void RequiredAttributeChanged() final; + void DisabledAttributeChanged() final; + + void InitializeTypeInParsing(); + void UpdateType(); + + void SubtreeHasChanged() final; + + void SetListAttributeTargetObserver(ListAttributeTargetObserver*); + void ResetListAttributeTargetObserver(); + void ParseMaxLengthAttribute(const AtomicString&); + void ParseMinLengthAttribute(const AtomicString&); + + // Returns null if this isn't associated with any radio button group. + RadioButtonGroupScope* GetRadioButtonGroupScope() const; + void AddToRadioButtonGroup(); + void RemoveFromRadioButtonGroup(); + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override; + + AtomicString name_; + // The value string in |value| value mode. + String non_attribute_value_; + unsigned size_; + // https://html.spec.whatwg.org/multipage/forms.html#concept-input-value-dirty-flag + unsigned has_dirty_value_ : 1; + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-checked + unsigned is_checked_ : 1; + // https://html.spec.whatwg.org/multipage/forms.html#concept-input-checked-dirty-flag + unsigned dirty_checkedness_ : 1; + unsigned is_indeterminate_ : 1; + unsigned is_activated_submit_ : 1; + unsigned autocomplete_ : 2; // AutoCompleteSetting + unsigned has_non_empty_list_ : 1; + unsigned state_restored_ : 1; + unsigned parsing_in_progress_ : 1; + unsigned can_receive_dropped_files_ : 1; + unsigned should_reveal_password_ : 1; + unsigned needs_to_update_view_value_ : 1; + unsigned is_placeholder_visible_ : 1; + unsigned has_been_password_field_ : 1; + Member<InputType> input_type_; + Member<InputTypeView> input_type_view_; + // The ImageLoader must be owned by this element because the loader code + // assumes that it lives as long as its owning element lives. If we move the + // loader into the ImageInput object we may delete the loader while this + // element lives on. + Member<HTMLImageLoader> image_loader_; + Member<ListAttributeTargetObserver> list_attribute_target_observer_; + + FRIEND_TEST_ALL_PREFIXES(HTMLInputElementTest, RadioKeyDownDCHECKFailure); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl new file mode 100644 index 00000000000..099fed4b526 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element.idl @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.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. + */ + +// https://html.spec.whatwg.org/#the-input-element + +enum SelectionMode { "select", "start", "end", "preserve" }; + +[ + HTMLConstructor, + ActiveScriptWrappable +] interface HTMLInputElement : HTMLElement { + [CEReactions, Reflect] attribute DOMString accept; + [CEReactions, Reflect] attribute DOMString alt; + [CEReactions, Reflect] attribute DOMString autocomplete; + [CEReactions, Reflect] attribute boolean autofocus; + [CEReactions, Reflect=checked] attribute boolean defaultChecked; + attribute boolean checked; + [CEReactions, Reflect] attribute DOMString dirName; + [CEReactions, Reflect] attribute boolean disabled; + [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form; + // The 'files' attribute is intentionally not readonly. + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=22682 + attribute FileList? files; + [CEReactions] attribute DOMString formAction; + [CEReactions, CustomElementCallbacks] attribute DOMString formEnctype; + [CEReactions, CustomElementCallbacks] attribute DOMString formMethod; + [CEReactions, Reflect] attribute boolean formNoValidate; + [CEReactions, Reflect] attribute DOMString formTarget; + [CEReactions, CustomElementCallbacks] attribute unsigned long height; + attribute boolean indeterminate; + readonly attribute HTMLElement? list; + [CEReactions, Reflect] attribute DOMString max; + [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute long maxLength; + [CEReactions, Reflect] attribute DOMString min; + [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute long minLength; + [CEReactions, Reflect] attribute boolean multiple; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString pattern; + [CEReactions, Reflect] attribute DOMString placeholder; + [CEReactions, Reflect] attribute boolean readOnly; + [CEReactions, Reflect] attribute boolean required; + [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute unsigned long size; + [CEReactions, Reflect, URL] attribute DOMString src; + [CEReactions, Reflect] attribute DOMString step; + [CEReactions, CustomElementCallbacks] attribute DOMString type; + [CEReactions, Reflect=value, CustomElementCallbacks] attribute DOMString defaultValue; + [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute [TreatNullAs=EmptyString] DOMString value; + [CEReactions, RaisesException=Setter, CustomElementCallbacks] attribute Date? valueAsDate; + [RaisesException=Setter, CustomElementCallbacks] attribute unrestricted double valueAsNumber; + // Note: The spec has valueLow and valueHigh for two-valued range controls. + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=13154 + [CEReactions, CustomElementCallbacks] attribute unsigned long width; + + [RaisesException, CustomElementCallbacks] void stepUp(optional long n = 1); + [RaisesException, CustomElementCallbacks] void stepDown(optional long n = 1); + + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); + + readonly attribute NodeList labels; + + void select(); + [RaisesException, ImplementedAs=selectionStartForBinding] attribute unsigned long? selectionStart; + [RaisesException, ImplementedAs=selectionEndForBinding] attribute unsigned long? selectionEnd; + [RaisesException, ImplementedAs=selectionDirectionForBinding] attribute DOMString? selectionDirection; + [RaisesException] void setRangeText(DOMString replacement); + [RaisesException] void setRangeText(DOMString replacement, + unsigned long start, + unsigned long end, + optional SelectionMode selectionMode = "preserve"); + [RaisesException, ImplementedAs=setSelectionRangeForBinding] + void setSelectionRange(unsigned long start, + unsigned long end, + optional DOMString direction); + + // obsolete members + // https://html.spec.whatwg.org/#HTMLInputElement-partial + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString useMap; + + // HTML Media Capture + // https://w3c.github.io/html-media-capture/#the-capture-attribute + [Measure, RuntimeEnabled=MediaCapture, Reflect] attribute DOMString capture; + + // Non-standard APIs + [Reflect, MeasureAs=PrefixedDirectoryAttribute] attribute boolean webkitdirectory; + [Reflect, MeasureAs=IncrementalAttribute] attribute boolean incremental; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc new file mode 100644 index 00000000000..6bbe2a3568e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_input_element_test.cc @@ -0,0 +1,206 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event_init.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/visual_viewport.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/html_body_element.h" +#include "third_party/blink/renderer/core/html/html_html_element.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" + +namespace blink { + +class HTMLInputElementTest : public PageTestBase { + protected: + HTMLInputElement& TestElement() { + Element* element = GetDocument().getElementById("test"); + DCHECK(element); + return ToHTMLInputElement(*element); + } +}; + +TEST_F(HTMLInputElementTest, FilteredDataListOptionsNoList) { + GetDocument().documentElement()->SetInnerHTMLFromString("<input id=test>"); + EXPECT_TRUE(TestElement().FilteredDataListOptions().IsEmpty()); + + GetDocument().documentElement()->SetInnerHTMLFromString( + "<input id=test list=dl1><datalist id=dl1></datalist>"); + EXPECT_TRUE(TestElement().FilteredDataListOptions().IsEmpty()); +} + +TEST_F(HTMLInputElementTest, FilteredDataListOptionsContain) { + GetDocument().documentElement()->SetInnerHTMLFromString( + "<input id=test value=BC list=dl2>" + "<datalist id=dl2>" + "<option>AbC DEF</option>" + "<option>VAX</option>" + "<option value=ghi>abc</option>" // Match to label, not value. + "</datalist>"); + auto options = TestElement().FilteredDataListOptions(); + EXPECT_EQ(2u, options.size()); + EXPECT_EQ("AbC DEF", options[0]->value().Utf8()); + EXPECT_EQ("ghi", options[1]->value().Utf8()); + + GetDocument().documentElement()->SetInnerHTMLFromString( + "<input id=test value=i list=dl2>" + "<datalist id=dl2>" + "<option>I</option>" + "<option>İ</option>" // LATIN CAPITAL LETTER I WITH DOT ABOVE + "<option>i</option>" // FULLWIDTH LATIN SMALL LETTER I + "</datalist>"); + options = TestElement().FilteredDataListOptions(); + EXPECT_EQ(2u, options.size()); + EXPECT_EQ("I", options[0]->value().Utf8()); + EXPECT_EQ(0x0130, options[1]->value()[0]); +} + +TEST_F(HTMLInputElementTest, FilteredDataListOptionsForMultipleEmail) { + GetDocument().documentElement()->SetInnerHTMLFromString(R"HTML( + <input id=test value='foo@example.com, tkent' list=dl3 type=email + multiple> + <datalist id=dl3> + <option>keishi@chromium.org</option> + <option>tkent@chromium.org</option> + </datalist> + )HTML"); + auto options = TestElement().FilteredDataListOptions(); + EXPECT_EQ(1u, options.size()); + EXPECT_EQ("tkent@chromium.org", options[0]->value().Utf8()); +} + +TEST_F(HTMLInputElementTest, create) { + auto* input = HTMLInputElement::Create(GetDocument(), + CreateElementFlags::ByCreateElement()); + EXPECT_NE(nullptr, input->UserAgentShadowRoot()); + + input = + HTMLInputElement::Create(GetDocument(), CreateElementFlags::ByParser()); + EXPECT_EQ(nullptr, input->UserAgentShadowRoot()); + input->ParserSetAttributes(Vector<Attribute>()); + EXPECT_NE(nullptr, input->UserAgentShadowRoot()); +} + +TEST_F(HTMLInputElementTest, NoAssertWhenMovedInNewDocument) { + Document* document_without_frame = Document::CreateForTest(); + EXPECT_EQ(nullptr, document_without_frame->GetPage()); + HTMLHtmlElement* html = HTMLHtmlElement::Create(*document_without_frame); + html->AppendChild(HTMLBodyElement::Create(*document_without_frame)); + + // Create an input element with type "range" inside a document without frame. + ToHTMLBodyElement(html->firstChild()) + ->SetInnerHTMLFromString("<input type='range' />"); + document_without_frame->AppendChild(html); + + std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(); + auto& document = page_holder->GetDocument(); + EXPECT_NE(nullptr, document.GetPage()); + + // Put the input element inside a document with frame. + document.body()->AppendChild(document_without_frame->body()->firstChild()); + + // Remove the input element and all refs to it so it gets deleted before the + // document. + // The assert in |EventHandlerRegistry::updateEventHandlerTargets()| should + // not be triggered. + document.body()->RemoveChild(document.body()->firstChild()); +} + +TEST_F(HTMLInputElementTest, DefaultToolTip) { + auto* input_without_form = + HTMLInputElement::Create(GetDocument(), CreateElementFlags()); + input_without_form->SetBooleanAttribute(HTMLNames::requiredAttr, true); + GetDocument().body()->AppendChild(input_without_form); + EXPECT_EQ("<<ValidationValueMissing>>", input_without_form->DefaultToolTip()); + + HTMLFormElement* form = HTMLFormElement::Create(GetDocument()); + GetDocument().body()->AppendChild(form); + auto* input_with_form = + HTMLInputElement::Create(GetDocument(), CreateElementFlags()); + input_with_form->SetBooleanAttribute(HTMLNames::requiredAttr, true); + form->AppendChild(input_with_form); + EXPECT_EQ("<<ValidationValueMissing>>", input_with_form->DefaultToolTip()); + + form->SetBooleanAttribute(HTMLNames::novalidateAttr, true); + EXPECT_EQ(String(), input_with_form->DefaultToolTip()); +} + +// crbug.com/589838 +TEST_F(HTMLInputElementTest, ImageTypeCrash) { + auto* input = HTMLInputElement::Create(GetDocument(), CreateElementFlags()); + input->setAttribute(HTMLNames::typeAttr, "image"); + input->EnsureFallbackContent(); + // Make sure ensurePrimaryContent() recreates UA shadow tree, and updating + // |value| doesn't crash. + input->EnsurePrimaryContent(); + input->setAttribute(HTMLNames::valueAttr, "aaa"); +} + +TEST_F(HTMLInputElementTest, RadioKeyDownDCHECKFailure) { + // crbug.com/697286 + GetDocument().body()->SetInnerHTMLFromString( + "<input type=radio name=g><input type=radio name=g>"); + HTMLInputElement& radio1 = + ToHTMLInputElement(*GetDocument().body()->firstChild()); + HTMLInputElement& radio2 = ToHTMLInputElement(*radio1.nextSibling()); + radio1.focus(); + // Make layout-dirty. + radio2.setAttribute(HTMLNames::styleAttr, "position:fixed"); + KeyboardEventInit init; + init.setKey("ArrowRight"); + radio1.DefaultEventHandler(new KeyboardEvent("keydown", init)); + EXPECT_EQ(GetDocument().ActiveElement(), &radio2); +} + +TEST_F(HTMLInputElementTest, DateTimeChooserSizeParamRespectsScale) { + GetDocument().SetCompatibilityMode(Document::kQuirksMode); + GetDocument().View()->GetFrame().GetPage()->GetVisualViewport().SetScale(2.f); + GetDocument().body()->SetInnerHTMLFromString( + "<input type='date' style='width:200px;height:50px' />"); + GetDocument().View()->UpdateAllLifecyclePhases(); + HTMLInputElement* input = + ToHTMLInputElement(GetDocument().body()->firstChild()); + + DateTimeChooserParameters params; + bool success = input->SetupDateTimeChooserParameters(params); + EXPECT_TRUE(success); + EXPECT_EQ("date", params.type); + EXPECT_EQ(IntRect(16, 16, 400, 100), params.anchor_rect_in_screen); +} + +TEST_F(HTMLInputElementTest, StepDownOverflow) { + auto* input = HTMLInputElement::Create(GetDocument(), CreateElementFlags()); + input->setAttribute(HTMLNames::typeAttr, "date"); + input->setAttribute(HTMLNames::minAttr, "2010-02-10"); + input->setAttribute(HTMLNames::stepAttr, "9223372036854775556"); + // InputType::applyStep() should not pass an out-of-range value to + // setValueAsDecimal, and WTF::msToYear() should not cause a DCHECK failure. + input->stepDown(1, ASSERT_NO_EXCEPTION); +} + +TEST_F(HTMLInputElementTest, CheckboxHasNoShadowRoot) { + GetDocument().body()->SetInnerHTMLFromString("<input type='checkbox' />"); + HTMLInputElement* input = + ToHTMLInputElement(GetDocument().body()->firstChild()); + EXPECT_EQ(nullptr, input->UserAgentShadowRoot()); +} + +TEST_F(HTMLInputElementTest, ChangingInputTypeCausesShadowRootToBeCreated) { + GetDocument().body()->SetInnerHTMLFromString("<input type='checkbox' />"); + HTMLInputElement* input = + ToHTMLInputElement(GetDocument().body()->firstChild()); + EXPECT_EQ(nullptr, input->UserAgentShadowRoot()); + input->setAttribute(HTMLNames::typeAttr, "text"); + EXPECT_NE(nullptr, input->UserAgentShadowRoot()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc new file mode 100644 index 00000000000..ea36f0bd3c8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.cc @@ -0,0 +1,250 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_label_element.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/editing/editing_utilities.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/editing/selection_controller.h" +#include "third_party/blink/renderer/core/editing/visible_selection.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/core/html/forms/listed_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" + +namespace blink { + +using namespace HTMLNames; + +inline HTMLLabelElement::HTMLLabelElement(Document& document) + : HTMLElement(labelTag, document), processing_click_(false) {} + +HTMLLabelElement* HTMLLabelElement::Create(Document& document) { + return new HTMLLabelElement(document); +} + +LabelableElement* HTMLLabelElement::control() const { + const AtomicString& control_id = getAttribute(forAttr); + if (control_id.IsNull()) { + // Search the children and descendants of the label element for a form + // element. + // per http://dev.w3.org/html5/spec/Overview.html#the-label-element + // the form element must be "labelable form-associated element". + for (LabelableElement& element : + Traversal<LabelableElement>::DescendantsOf(*this)) { + if (element.SupportLabels()) { + if (!element.IsFormControlElement()) { + UseCounter::Count( + GetDocument(), + WebFeature::kHTMLLabelElementControlForNonFormAssociatedElement); + } + return &element; + } + } + return nullptr; + } + + if (!IsInTreeScope()) + return nullptr; + + if (Element* element = GetTreeScope().getElementById(control_id)) { + if (IsLabelableElement(*element) && + ToLabelableElement(*element).SupportLabels()) { + if (!element->IsFormControlElement()) { + UseCounter::Count( + GetDocument(), + WebFeature::kHTMLLabelElementControlForNonFormAssociatedElement); + } + return ToLabelableElement(element); + } + } + + return nullptr; +} + +HTMLFormElement* HTMLLabelElement::form() const { + if (LabelableElement* control = this->control()) { + return control->IsFormControlElement() + ? ToHTMLFormControlElement(control)->Form() + : nullptr; + } + return nullptr; +} + +void HTMLLabelElement::SetActive(bool down) { + if (down != IsActive()) { + // Update our status first. + HTMLElement::SetActive(down); + } + + // Also update our corresponding control. + HTMLElement* control_element = control(); + if (control_element && control_element->IsActive() != IsActive()) + control_element->SetActive(IsActive()); +} + +void HTMLLabelElement::SetHovered(bool over) { + if (over != IsHovered()) { + // Update our status first. + HTMLElement::SetHovered(over); + } + + // Also update our corresponding control. + HTMLElement* element = control(); + if (element && element->IsHovered() != IsHovered()) + element->SetHovered(IsHovered()); +} + +bool HTMLLabelElement::IsInteractiveContent() const { + return true; +} + +bool HTMLLabelElement::IsInInteractiveContent(Node* node) const { + if (!IsShadowIncludingInclusiveAncestorOf(node)) + return false; + while (node && this != node) { + if (node->IsHTMLElement() && ToHTMLElement(node)->IsInteractiveContent()) + return true; + node = node->ParentOrShadowHostNode(); + } + return false; +} + +void HTMLLabelElement::DefaultEventHandler(Event* evt) { + if (evt->type() == EventTypeNames::click && !processing_click_) { + HTMLElement* element = control(); + + // If we can't find a control or if the control received the click + // event, then there's no need for us to do anything. + if (!element || + (evt->target() && element->IsShadowIncludingInclusiveAncestorOf( + evt->target()->ToNode()))) + return; + + if (evt->target() && IsInInteractiveContent(evt->target()->ToNode())) + return; + + // Behaviour of label element is as follows: + // - If there is double click, two clicks will be passed to control + // element. Control element will *not* be focused. + // - If there is selection of label element by dragging, no click + // event is passed. Also, no focus on control element. + // - If there is already a selection on label element and then label + // is clicked, then click event is passed to control element and + // control element is focused. + + bool is_label_text_selected = false; + + // If the click is not simulated and the text of the label element + // is selected by dragging over it, then return without passing the + // click event to control element. + // Note: check if it is a MouseEvent because a click event may + // not be an instance of a MouseEvent if created by document.createEvent(). + if (evt->IsMouseEvent() && ToMouseEvent(evt)->HasPosition()) { + if (LocalFrame* frame = GetDocument().GetFrame()) { + // Check if there is a selection and click is not on the + // selection. + if (GetLayoutObject() && GetLayoutObject()->IsSelectable() && + frame->Selection() + .ComputeVisibleSelectionInDOMTreeDeprecated() + .IsRange() && + !frame->GetEventHandler() + .GetSelectionController() + .MouseDownWasSingleClickInSelection() && + evt->target()->ToNode()->CanStartSelection()) + is_label_text_selected = true; + // If selection is there and is single click i.e. text is + // selected by dragging over label text, then return. + // Click count >=2, meaning double click or triple click, + // should pass click event to control element. + // Only in case of drag, *neither* we pass the click event, + // *nor* we focus the control element. + if (is_label_text_selected && ToMouseEvent(evt)->ClickCount() == 1) + return; + } + } + + processing_click_ = true; + + GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + if (element->IsMouseFocusable()) { + // If the label is *not* selected, or if the click happened on + // selection of label, only then focus the control element. + // In case of double click or triple click, selection will be there, + // so do not focus the control element. + if (!is_label_text_selected) { + element->focus(FocusParams(SelectionBehaviorOnFocus::kRestore, + kWebFocusTypeMouse, nullptr)); + } + } + + // Click the corresponding control. + element->DispatchSimulatedClick(evt); + + processing_click_ = false; + + evt->SetDefaultHandled(); + } + + HTMLElement::DefaultEventHandler(evt); +} + +bool HTMLLabelElement::HasActivationBehavior() const { + return true; +} + +bool HTMLLabelElement::WillRespondToMouseClickEvents() { + if (control() && control()->WillRespondToMouseClickEvents()) + return true; + + return HTMLElement::WillRespondToMouseClickEvents(); +} + +void HTMLLabelElement::focus(const FocusParams& params) { + GetDocument().UpdateStyleAndLayoutTreeForNode(this); + if (IsFocusable()) { + HTMLElement::focus(params); + return; + } + // To match other browsers, always restore previous selection. + if (HTMLElement* element = control()) { + element->focus(FocusParams(SelectionBehaviorOnFocus::kRestore, params.type, + params.source_capabilities, params.options)); + } +} + +void HTMLLabelElement::AccessKeyAction(bool send_mouse_events) { + if (HTMLElement* element = control()) + element->AccessKeyAction(send_mouse_events); + else + HTMLElement::AccessKeyAction(send_mouse_events); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.h new file mode 100644 index 00000000000..9bfb702444a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LABEL_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LABEL_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class LabelableElement; + +class CORE_EXPORT HTMLLabelElement final : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLLabelElement* Create(Document&); + LabelableElement* control() const; + HTMLFormElement* form() const; + + bool WillRespondToMouseClickEvents() override; + + private: + explicit HTMLLabelElement(Document&); + bool IsInInteractiveContent(Node*) const; + + bool IsInteractiveContent() const override; + void AccessKeyAction(bool send_mouse_events) override; + + // Overridden to update the hover/active state of the corresponding control. + void SetActive(bool = true) override; + void SetHovered(bool = true) override; + + // Overridden to either click() or focus() the corresponding control. + void DefaultEventHandler(Event*) override; + bool HasActivationBehavior() const override; + + void focus(const FocusParams&) override; + + bool processing_click_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LABEL_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl new file mode 100644 index 00000000000..254cbd9a6e8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_label_element.idl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * + * 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. + */ + +// https://html.spec.whatwg.org/#the-label-element +[HTMLConstructor] +interface HTMLLabelElement : HTMLElement { + readonly attribute HTMLFormElement? form; + [CEReactions, Reflect=for] attribute DOMString htmlFor; + readonly attribute HTMLElement? control; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc new file mode 100644 index 00000000000..4317588c878 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.cc @@ -0,0 +1,82 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_legend_element.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/html/forms/html_field_set_element.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +using namespace HTMLNames; + +inline HTMLLegendElement::HTMLLegendElement(Document& document) + : HTMLElement(legendTag, document) {} + +DEFINE_NODE_FACTORY(HTMLLegendElement) + +HTMLFormControlElement* HTMLLegendElement::AssociatedControl() { + // Check if there's a fieldset belonging to this legend. + HTMLFieldSetElement* fieldset = + Traversal<HTMLFieldSetElement>::FirstAncestor(*this); + if (!fieldset) + return nullptr; + + // Find first form element inside the fieldset that is not a legend element. + // FIXME: Should we consider tabindex? + return Traversal<HTMLFormControlElement>::Next(*fieldset, fieldset); +} + +void HTMLLegendElement::focus(const FocusParams& params) { + GetDocument().UpdateStyleAndLayoutTreeForNode(this); + if (IsFocusable()) { + Element::focus(params); + return; + } + + // To match other browsers' behavior, never restore previous selection. + if (HTMLFormControlElement* control = AssociatedControl()) { + control->focus(FocusParams(SelectionBehaviorOnFocus::kReset, params.type, + params.source_capabilities, params.options)); + } +} + +void HTMLLegendElement::AccessKeyAction(bool send_mouse_events) { + if (HTMLFormControlElement* control = AssociatedControl()) + control->AccessKeyAction(send_mouse_events); +} + +HTMLFormElement* HTMLLegendElement::form() const { + // According to the specification, If the legend has a fieldset element as + // its parent, then the form attribute must return the same value as the + // form attribute on that fieldset element. Otherwise, it must return null. + if (auto* fieldset = ToHTMLFieldSetElementOrNull(parentNode())) + return fieldset->formOwner(); + return nullptr; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h new file mode 100644 index 00000000000..c69a718b330 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LEGEND_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LEGEND_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class HTMLFormControlElement; + +class HTMLLegendElement final : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + DECLARE_NODE_FACTORY(HTMLLegendElement); + + HTMLFormElement* form() const; + + private: + explicit HTMLLegendElement(Document&); + + // Control in the legend's fieldset that gets focus and access key. + HTMLFormControlElement* AssociatedControl(); + + void AccessKeyAction(bool send_mouse_events) override; + void focus(const FocusParams&) override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_LEGEND_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl new file mode 100644 index 00000000000..909b3ab17f3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_legend_element.idl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * + * 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. + */ + +// https://html.spec.whatwg.org/#the-legend-element +[HTMLConstructor] +interface HTMLLegendElement : HTMLElement { + readonly attribute HTMLFormElement? form; + + // obsolete members + // https://html.spec.whatwg.org/#HTMLLegendElement-partial + [CEReactions, Reflect] attribute DOMString align; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc new file mode 100644 index 00000000000..37bebed4f77 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc @@ -0,0 +1,165 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights + * reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" + +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/editing/editing_utilities.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/html/html_slot_element.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/character_names.h" + +namespace blink { + +using namespace HTMLNames; + +inline HTMLOptGroupElement::HTMLOptGroupElement(Document& document) + : HTMLElement(optgroupTag, document) {} + +// An explicit empty destructor should be in HTMLOptGroupElement.cpp, because +// if an implicit destructor is used or an empty destructor is defined in +// HTMLOptGroupElement.h, when including HTMLOptGroupElement.h, +// msvc tries to expand the destructor and causes +// a compile error because of lack of ComputedStyle definition. +HTMLOptGroupElement::~HTMLOptGroupElement() = default; + +HTMLOptGroupElement* HTMLOptGroupElement::Create(Document& document) { + HTMLOptGroupElement* opt_group_element = new HTMLOptGroupElement(document); + opt_group_element->EnsureUserAgentShadowRoot(); + return opt_group_element; +} + +// static +bool HTMLOptGroupElement::CanAssignToOptGroupSlot(const Node& node) { + return node.HasTagName(optionTag) || node.HasTagName(hrTag); +} + +bool HTMLOptGroupElement::IsDisabledFormControl() const { + return FastHasAttribute(disabledAttr); +} + +void HTMLOptGroupElement::ParseAttribute( + const AttributeModificationParams& params) { + HTMLElement::ParseAttribute(params); + + if (params.name == disabledAttr) { + PseudoStateChanged(CSSSelector::kPseudoDisabled); + PseudoStateChanged(CSSSelector::kPseudoEnabled); + } else if (params.name == labelAttr) { + UpdateGroupLabel(); + } +} + +bool HTMLOptGroupElement::SupportsFocus() const { + HTMLSelectElement* select = OwnerSelectElement(); + if (select && select->UsesMenuList()) + return false; + return HTMLElement::SupportsFocus(); +} + +bool HTMLOptGroupElement::MatchesEnabledPseudoClass() const { + return !IsDisabledFormControl(); +} + +Node::InsertionNotificationRequest HTMLOptGroupElement::InsertedInto( + ContainerNode* insertion_point) { + HTMLElement::InsertedInto(insertion_point); + if (HTMLSelectElement* select = OwnerSelectElement()) { + if (insertion_point == select) + select->OptGroupInsertedOrRemoved(*this); + } + return kInsertionDone; +} + +void HTMLOptGroupElement::RemovedFrom(ContainerNode* insertion_point) { + if (auto* select = ToHTMLSelectElementOrNull(*insertion_point)) { + if (!parentNode()) + select->OptGroupInsertedOrRemoved(*this); + } + HTMLElement::RemovedFrom(insertion_point); +} + +String HTMLOptGroupElement::GroupLabelText() const { + String item_text = getAttribute(labelAttr); + + // In WinIE, leading and trailing whitespace is ignored in options and + // optgroups. We match this behavior. + item_text = item_text.StripWhiteSpace(); + // We want to collapse our whitespace too. This will match other browsers. + item_text = item_text.SimplifyWhiteSpace(); + + return item_text; +} + +HTMLSelectElement* HTMLOptGroupElement::OwnerSelectElement() const { + // TODO(tkent): We should return only the parent <select>. + return Traversal<HTMLSelectElement>::FirstAncestor(*this); +} + +String HTMLOptGroupElement::DefaultToolTip() const { + if (HTMLSelectElement* select = OwnerSelectElement()) + return select->DefaultToolTip(); + return String(); +} + +void HTMLOptGroupElement::AccessKeyAction(bool) { + HTMLSelectElement* select = OwnerSelectElement(); + // send to the parent to bring focus to the list box + if (select && !select->IsFocused()) + select->AccessKeyAction(false); +} + +void HTMLOptGroupElement::DidAddUserAgentShadowRoot(ShadowRoot& root) { + DEFINE_STATIC_LOCAL(AtomicString, label_padding, ("0 2px 1px 2px")); + DEFINE_STATIC_LOCAL(AtomicString, label_min_height, ("1.2em")); + HTMLDivElement* label = HTMLDivElement::Create(GetDocument()); + label->setAttribute(roleAttr, AtomicString("group")); + label->setAttribute(aria_labelAttr, AtomicString()); + label->SetInlineStyleProperty(CSSPropertyPadding, label_padding); + label->SetInlineStyleProperty(CSSPropertyMinHeight, label_min_height); + label->SetIdAttribute(ShadowElementNames::OptGroupLabel()); + root.AppendChild(label); + + root.AppendChild( + HTMLSlotElement::CreateUserAgentCustomAssignSlot(GetDocument())); +} + +void HTMLOptGroupElement::UpdateGroupLabel() { + const String& label_text = GroupLabelText(); + HTMLDivElement& label = OptGroupLabelElement(); + label.setTextContent(label_text); + label.setAttribute(aria_labelAttr, AtomicString(label_text)); +} + +HTMLDivElement& HTMLOptGroupElement::OptGroupLabelElement() const { + return *ToHTMLDivElementOrDie(UserAgentShadowRoot()->getElementById( + ShadowElementNames::OptGroupLabel())); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h new file mode 100644 index 00000000000..52cb07a2a04 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPT_GROUP_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPT_GROUP_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class HTMLSelectElement; +class HTMLDivElement; + +class CORE_EXPORT HTMLOptGroupElement final : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLOptGroupElement* Create(Document&); + + bool IsDisabledFormControl() const override; + String DefaultToolTip() const override; + HTMLSelectElement* OwnerSelectElement() const; + + String GroupLabelText() const; + HTMLDivElement& OptGroupLabelElement() const; + + // Used for slot assignment. + static bool CanAssignToOptGroupSlot(const Node&); + + private: + explicit HTMLOptGroupElement(Document&); + ~HTMLOptGroupElement() override; + + bool SupportsFocus() const override; + void ParseAttribute(const AttributeModificationParams&) override; + void AccessKeyAction(bool send_mouse_events) override; + void DidAddUserAgentShadowRoot(ShadowRoot&) override; + bool MatchesEnabledPseudoClass() const override; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void RemovedFrom(ContainerNode*) override; + + void UpdateGroupLabel(); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPT_GROUP_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl new file mode 100644 index 00000000000..91f7a64eff6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_opt_group_element.idl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. 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. + */ + +// https://html.spec.whatwg.org/#the-optgroup-element +[HTMLConstructor] +interface HTMLOptGroupElement : HTMLElement { + [CEReactions, Reflect] attribute boolean disabled; + [CEReactions, Reflect] attribute DOMString label; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc new file mode 100644 index 00000000000..c12a3710774 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.cc @@ -0,0 +1,412 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Motorola Mobility, Inc. 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 "third_party/blink/renderer/core/html/forms/html_option_element.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/dom/node_traversal.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" +#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +using namespace HTMLNames; + +HTMLOptionElement::HTMLOptionElement(Document& document) + : HTMLElement(optionTag, document), is_selected_(false) {} + +// An explicit empty destructor should be in HTMLOptionElement.cpp, because +// if an implicit destructor is used or an empty destructor is defined in +// HTMLOptionElement.h, when including HTMLOptionElement.h, +// msvc tries to expand the destructor and causes +// a compile error because of lack of ComputedStyle definition. +HTMLOptionElement::~HTMLOptionElement() = default; + +HTMLOptionElement* HTMLOptionElement::Create(Document& document) { + HTMLOptionElement* option = new HTMLOptionElement(document); + option->EnsureUserAgentShadowRoot(); + return option; +} + +HTMLOptionElement* HTMLOptionElement::CreateForJSConstructor( + Document& document, + const String& data, + const AtomicString& value, + bool default_selected, + bool selected, + ExceptionState& exception_state) { + HTMLOptionElement* element = new HTMLOptionElement(document); + element->EnsureUserAgentShadowRoot(); + if (!data.IsEmpty()) { + element->AppendChild(Text::Create(document, data), exception_state); + if (exception_state.HadException()) + return nullptr; + } + + if (!value.IsNull()) + element->setValue(value); + if (default_selected) + element->setAttribute(selectedAttr, g_empty_atom); + element->SetSelected(selected); + + return element; +} + +void HTMLOptionElement::AttachLayoutTree(AttachContext& context) { + AttachContext option_context(context); + if (!GetNonAttachedStyle() && ParentComputedStyle()) { + if (HTMLSelectElement* select = OwnerSelectElement()) + select->UpdateListOnLayoutObject(); + SetNonAttachedStyle(StyleForLayoutObject()); + } + HTMLElement::AttachLayoutTree(option_context); +} + +bool HTMLOptionElement::SupportsFocus() const { + HTMLSelectElement* select = OwnerSelectElement(); + if (select && select->UsesMenuList()) + return false; + return HTMLElement::SupportsFocus(); +} + +bool HTMLOptionElement::MatchesDefaultPseudoClass() const { + return FastHasAttribute(selectedAttr); +} + +bool HTMLOptionElement::MatchesEnabledPseudoClass() const { + return !IsDisabledFormControl(); +} + +String HTMLOptionElement::DisplayLabel() const { + Document& document = GetDocument(); + String text; + + // WinIE does not use the label attribute, so as a quirk, we ignore it. + if (!document.InQuirksMode()) + text = FastGetAttribute(labelAttr); + + // FIXME: The following treats an element with the label attribute set to + // the empty string the same as an element with no label attribute at all. + // Is that correct? If it is, then should the label function work the same + // way? + if (text.IsEmpty()) + text = CollectOptionInnerText(); + + return text.StripWhiteSpace(IsHTMLSpace<UChar>) + .SimplifyWhiteSpace(IsHTMLSpace<UChar>); +} + +String HTMLOptionElement::text() const { + return CollectOptionInnerText() + .StripWhiteSpace(IsHTMLSpace<UChar>) + .SimplifyWhiteSpace(IsHTMLSpace<UChar>); +} + +void HTMLOptionElement::setText(const String& text) { + // Changing the text causes a recalc of a select's items, which will reset the + // selected index to the first item if the select is single selection with a + // menu list. We attempt to preserve the selected item. + HTMLSelectElement* select = OwnerSelectElement(); + bool select_is_menu_list = select && select->UsesMenuList(); + int old_selected_index = select_is_menu_list ? select->selectedIndex() : -1; + + setTextContent(text); + + if (select_is_menu_list && select->selectedIndex() != old_selected_index) + select->setSelectedIndex(old_selected_index); +} + +void HTMLOptionElement::AccessKeyAction(bool) { + if (HTMLSelectElement* select = OwnerSelectElement()) + select->SelectOptionByAccessKey(this); +} + +int HTMLOptionElement::index() const { + // It would be faster to cache the index, but harder to get it right in all + // cases. + + HTMLSelectElement* select_element = OwnerSelectElement(); + if (!select_element) + return 0; + + int option_index = 0; + for (auto* const option : select_element->GetOptionList()) { + if (option == this) + return option_index; + ++option_index; + } + + return 0; +} + +int HTMLOptionElement::ListIndex() const { + if (HTMLSelectElement* select_element = OwnerSelectElement()) + return select_element->ListIndexForOption(*this); + return -1; +} + +void HTMLOptionElement::ParseAttribute( + const AttributeModificationParams& params) { + const QualifiedName& name = params.name; + if (name == valueAttr) { + if (HTMLDataListElement* data_list = OwnerDataListElement()) + data_list->OptionElementChildrenChanged(); + } else if (name == disabledAttr) { + if (params.old_value.IsNull() != params.new_value.IsNull()) { + PseudoStateChanged(CSSSelector::kPseudoDisabled); + PseudoStateChanged(CSSSelector::kPseudoEnabled); + if (LayoutObject* o = GetLayoutObject()) + o->InvalidateIfControlStateChanged(kEnabledControlState); + } + } else if (name == selectedAttr) { + if (params.old_value.IsNull() != params.new_value.IsNull() && !is_dirty_) + SetSelected(!params.new_value.IsNull()); + PseudoStateChanged(CSSSelector::kPseudoDefault); + } else if (name == labelAttr) { + UpdateLabel(); + } else { + HTMLElement::ParseAttribute(params); + } +} + +String HTMLOptionElement::value() const { + const AtomicString& value = FastGetAttribute(valueAttr); + if (!value.IsNull()) + return value; + return CollectOptionInnerText() + .StripWhiteSpace(IsHTMLSpace<UChar>) + .SimplifyWhiteSpace(IsHTMLSpace<UChar>); +} + +void HTMLOptionElement::setValue(const AtomicString& value) { + setAttribute(valueAttr, value); +} + +bool HTMLOptionElement::Selected() const { + return is_selected_; +} + +void HTMLOptionElement::SetSelected(bool selected) { + if (is_selected_ == selected) + return; + + SetSelectedState(selected); + + if (HTMLSelectElement* select = OwnerSelectElement()) + select->OptionSelectionStateChanged(this, selected); +} + +bool HTMLOptionElement::selectedForBinding() const { + return Selected(); +} + +void HTMLOptionElement::setSelectedForBinding(bool selected) { + bool was_selected = is_selected_; + SetSelected(selected); + + // As of December 2015, the HTML specification says the dirtiness becomes + // true by |selected| setter unconditionally. However it caused a real bug, + // crbug.com/570367, and is not compatible with other browsers. + // Firefox seems not to set dirtiness if an option is owned by a select + // element and selectedness is not changed. + if (OwnerSelectElement() && was_selected == is_selected_) + return; + + is_dirty_ = true; +} + +void HTMLOptionElement::SetSelectedState(bool selected) { + if (is_selected_ == selected) + return; + + is_selected_ = selected; + PseudoStateChanged(CSSSelector::kPseudoChecked); + + if (HTMLSelectElement* select = OwnerSelectElement()) { + select->InvalidateSelectedItems(); + + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { + // If there is a layoutObject (most common), fire accessibility + // notifications only when it's a listbox (and not a menu list). If + // there's no layoutObject, fire them anyway just to be safe (to make sure + // the AX tree is in sync). + if (!select->GetLayoutObject() || + select->GetLayoutObject()->IsListBox()) { + cache->ListboxOptionStateChanged(this); + cache->ListboxSelectedChildrenChanged(select); + } + } + } +} + +void HTMLOptionElement::SetDirty(bool value) { + is_dirty_ = value; +} + +void HTMLOptionElement::ChildrenChanged(const ChildrenChange& change) { + if (HTMLDataListElement* data_list = OwnerDataListElement()) + data_list->OptionElementChildrenChanged(); + else if (HTMLSelectElement* select = OwnerSelectElement()) + select->OptionElementChildrenChanged(*this); + UpdateLabel(); + HTMLElement::ChildrenChanged(change); +} + +HTMLDataListElement* HTMLOptionElement::OwnerDataListElement() const { + return Traversal<HTMLDataListElement>::FirstAncestor(*this); +} + +HTMLSelectElement* HTMLOptionElement::OwnerSelectElement() const { + if (!parentNode()) + return nullptr; + if (auto* select = ToHTMLSelectElementOrNull(*parentNode())) + return select; + if (IsHTMLOptGroupElement(*parentNode())) + return ToHTMLSelectElementOrNull(parentNode()->parentNode()); + return nullptr; +} + +String HTMLOptionElement::label() const { + const AtomicString& label = FastGetAttribute(labelAttr); + if (!label.IsNull()) + return label; + return CollectOptionInnerText() + .StripWhiteSpace(IsHTMLSpace<UChar>) + .SimplifyWhiteSpace(IsHTMLSpace<UChar>); +} + +void HTMLOptionElement::setLabel(const AtomicString& label) { + setAttribute(labelAttr, label); +} + +String HTMLOptionElement::TextIndentedToRespectGroupLabel() const { + ContainerNode* parent = parentNode(); + if (parent && IsHTMLOptGroupElement(*parent)) + return " " + DisplayLabel(); + return DisplayLabel(); +} + +bool HTMLOptionElement::OwnElementDisabled() const { + return FastHasAttribute(disabledAttr); +} + +bool HTMLOptionElement::IsDisabledFormControl() const { + if (OwnElementDisabled()) + return true; + if (Element* parent = parentElement()) + return IsHTMLOptGroupElement(*parent) && parent->IsDisabledFormControl(); + return false; +} + +String HTMLOptionElement::DefaultToolTip() const { + if (HTMLSelectElement* select = OwnerSelectElement()) + return select->DefaultToolTip(); + return String(); +} + +Node::InsertionNotificationRequest HTMLOptionElement::InsertedInto( + ContainerNode* insertion_point) { + HTMLElement::InsertedInto(insertion_point); + if (HTMLSelectElement* select = OwnerSelectElement()) { + if (insertion_point == select || (IsHTMLOptGroupElement(*insertion_point) && + insertion_point->parentNode() == select)) + select->OptionInserted(*this, is_selected_); + } + return kInsertionDone; +} + +void HTMLOptionElement::RemovedFrom(ContainerNode* insertion_point) { + if (auto* select = ToHTMLSelectElementOrNull(*insertion_point)) { + if (!parentNode() || IsHTMLOptGroupElement(*parentNode())) + select->OptionRemoved(*this); + } else if (IsHTMLOptGroupElement(*insertion_point)) { + if (auto* select = ToHTMLSelectElementOrNull(insertion_point->parentNode())) + select->OptionRemoved(*this); + } + HTMLElement::RemovedFrom(insertion_point); +} + +String HTMLOptionElement::CollectOptionInnerText() const { + StringBuilder text; + for (Node* node = firstChild(); node;) { + if (node->IsTextNode()) + text.Append(node->nodeValue()); + // Text nodes inside script elements are not part of the option text. + if (node->IsElementNode() && ToElement(node)->IsScriptElement()) + node = NodeTraversal::NextSkippingChildren(*node, this); + else + node = NodeTraversal::Next(*node, this); + } + return text.ToString(); +} + +HTMLFormElement* HTMLOptionElement::form() const { + if (HTMLSelectElement* select_element = OwnerSelectElement()) + return select_element->formOwner(); + + return nullptr; +} + +void HTMLOptionElement::DidAddUserAgentShadowRoot(ShadowRoot& root) { + UpdateLabel(); +} + +void HTMLOptionElement::UpdateLabel() { + if (ShadowRoot* root = UserAgentShadowRoot()) + root->setTextContent(DisplayLabel()); +} + +bool HTMLOptionElement::SpatialNavigationFocused() const { + HTMLSelectElement* select = OwnerSelectElement(); + if (!select || !select->IsFocused()) + return false; + return select->SpatialNavigationFocusedOption() == this; +} + +bool HTMLOptionElement::IsDisplayNone() const { + const ComputedStyle* style = GetComputedStyle(); + return !style || style->Display() == EDisplay::kNone; +} + +String HTMLOptionElement::innerText() { + // A workaround for crbug.com/424578. We add ShadowRoot to an OPTION, but + // innerText behavior for Shadow DOM is unclear. We just return the same + // string before adding ShadowRoot. + return textContent(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h new file mode 100644 index 00000000000..3ef0a2e9ab3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2010, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTION_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTION_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class ExceptionState; +class HTMLDataListElement; +class HTMLSelectElement; + +class CORE_EXPORT HTMLOptionElement final : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLOptionElement* Create(Document&); + static HTMLOptionElement* CreateForJSConstructor(Document&, + const String& data, + const AtomicString& value, + bool default_selected, + bool selected, + ExceptionState&); + + // A text to be shown to users. The difference from |label()| is |label()| + // returns an empty string if |label| content attribute is empty. + // |displayLabel()| returns the value string in that case. + String DisplayLabel() const; + + // |text| IDL attribute implementations. + String text() const; + void setText(const String&); + + int index() const; + + String value() const; + void setValue(const AtomicString&); + + bool Selected() const; + void SetSelected(bool); + bool selectedForBinding() const; + void setSelectedForBinding(bool); + + HTMLDataListElement* OwnerDataListElement() const; + HTMLSelectElement* OwnerSelectElement() const; + + String label() const; + void setLabel(const AtomicString&); + + bool OwnElementDisabled() const; + + bool IsDisabledFormControl() const override; + String DefaultToolTip() const override; + + String TextIndentedToRespectGroupLabel() const; + + // Update 'selectedness'. + void SetSelectedState(bool); + // Update 'dirtiness'. + void SetDirty(bool); + + HTMLFormElement* form() const; + bool SpatialNavigationFocused() const; + + bool IsDisplayNone() const; + + int ListIndex() const; + + private: + explicit HTMLOptionElement(Document&); + ~HTMLOptionElement() override; + + bool SupportsFocus() const override; + bool MatchesDefaultPseudoClass() const override; + bool MatchesEnabledPseudoClass() const override; + void AttachLayoutTree(AttachContext&) override; + void ParseAttribute(const AttributeModificationParams&) override; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void RemovedFrom(ContainerNode*) override; + void AccessKeyAction(bool) override; + void ChildrenChanged(const ChildrenChange&) override; + String innerText() override; + + void DidAddUserAgentShadowRoot(ShadowRoot&) override; + + String CollectOptionInnerText() const; + + void UpdateLabel(); + + // Represents 'selectedness'. + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-selectedness + bool is_selected_; + // Represents 'dirtiness'. + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-dirtiness + bool is_dirty_ = false; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTION_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl new file mode 100644 index 00000000000..39c2fe25650 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_option_element.idl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2006, 2007, 2010 Apple, Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * + * 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. + */ + +// https://html.spec.whatwg.org/#the-option-element + +[ + HTMLConstructor, + NamedConstructor=Option(optional DOMString data = null, + optional DOMString value = null, + optional boolean defaultSelected = false, + optional boolean selected = false), + ConstructorCallWith=Document, + RaisesException=Constructor +] interface HTMLOptionElement : HTMLElement { + [CEReactions, Reflect] attribute boolean disabled; + readonly attribute HTMLFormElement? form; + [CEReactions] attribute DOMString label; + [CEReactions, Reflect=selected] attribute boolean defaultSelected; + [ImplementedAs=selectedForBinding] attribute boolean selected; + [CEReactions] attribute DOMString value; + + [CEReactions] attribute DOMString text; + readonly attribute long index; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc new file mode 100644 index 00000000000..e195f8aad68 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.cc @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2006, 2011, 2012 Apple Computer, Inc. + * Copyright (C) 2014 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 "third_party/blink/renderer/core/html/forms/html_options_collection.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/bindings/core/v8/html_element_or_long.h" +#include "third_party/blink/renderer/bindings/core/v8/html_option_element_or_html_opt_group_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" + +namespace blink { + +HTMLOptionsCollection::HTMLOptionsCollection(ContainerNode& select) + : HTMLCollection(select, kSelectOptions, kDoesNotOverrideItemAfter) { + DCHECK(IsHTMLSelectElement(select)); +} + +void HTMLOptionsCollection::SupportedPropertyNames(Vector<String>& names) { + // As per + // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmloptionscollection: + // The supported property names consist of the non-empty values of all the id + // and name attributes of all the elements represented by the collection, in + // tree order, ignoring later duplicates, with the id of an element preceding + // its name if it contributes both, they differ from each other, and neither + // is the duplicate of an earlier entry. + HashSet<AtomicString> existing_names; + unsigned length = this->length(); + for (unsigned i = 0; i < length; ++i) { + Element* element = item(i); + DCHECK(element); + const AtomicString& id_attribute = element->GetIdAttribute(); + if (!id_attribute.IsEmpty()) { + HashSet<AtomicString>::AddResult add_result = + existing_names.insert(id_attribute); + if (add_result.is_new_entry) + names.push_back(id_attribute); + } + const AtomicString& name_attribute = element->GetNameAttribute(); + if (!name_attribute.IsEmpty()) { + HashSet<AtomicString>::AddResult add_result = + existing_names.insert(name_attribute); + if (add_result.is_new_entry) + names.push_back(name_attribute); + } + } +} + +HTMLOptionsCollection* HTMLOptionsCollection::Create(ContainerNode& select, + CollectionType) { + return new HTMLOptionsCollection(select); +} + +void HTMLOptionsCollection::add( + const HTMLOptionElementOrHTMLOptGroupElement& element, + const HTMLElementOrLong& before, + ExceptionState& exception_state) { + ToHTMLSelectElement(ownerNode()).add(element, before, exception_state); +} + +void HTMLOptionsCollection::remove(int index) { + ToHTMLSelectElement(ownerNode()).remove(index); +} + +int HTMLOptionsCollection::selectedIndex() const { + return ToHTMLSelectElement(ownerNode()).selectedIndex(); +} + +void HTMLOptionsCollection::setSelectedIndex(int index) { + ToHTMLSelectElement(ownerNode()).setSelectedIndex(index); +} + +void HTMLOptionsCollection::setLength(unsigned length, + ExceptionState& exception_state) { + ToHTMLSelectElement(ownerNode()).setLength(length, exception_state); +} + +bool HTMLOptionsCollection::AnonymousIndexedSetter( + unsigned index, + HTMLOptionElement* value, + ExceptionState& exception_state) { + HTMLSelectElement& base = ToHTMLSelectElement(ownerNode()); + if (!value) { // undefined or null + base.remove(index); + return true; + } + base.SetOption(index, value, exception_state); + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h new file mode 100644 index 00000000000..665d0eb431d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTIONS_COLLECTION_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTIONS_COLLECTION_H_ + +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/html_collection.h" + +namespace blink { + +class ExceptionState; +class HTMLOptionElementOrHTMLOptGroupElement; +class HTMLElementOrLong; + +class HTMLOptionsCollection final : public HTMLCollection { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLOptionsCollection* Create(ContainerNode&, CollectionType); + + HTMLOptionElement* item(unsigned offset) const { + return ToHTMLOptionElement(HTMLCollection::item(offset)); + } + + void add(const HTMLOptionElementOrHTMLOptGroupElement&, + const HTMLElementOrLong&, + ExceptionState&); + void remove(int index); + + int selectedIndex() const; + void setSelectedIndex(int); + + void setLength(unsigned, ExceptionState&); + bool AnonymousIndexedSetter(unsigned, HTMLOptionElement*, ExceptionState&); + + bool ElementMatches(const HTMLElement&) const; + + private: + explicit HTMLOptionsCollection(ContainerNode&); + + void SupportedPropertyNames(Vector<String>& names) override; +}; + +DEFINE_TYPE_CASTS(HTMLOptionsCollection, + LiveNodeListBase, + collection, + collection->GetType() == kSelectOptions, + collection.GetType() == kSelectOptions); + +inline bool HTMLOptionsCollection::ElementMatches( + const HTMLElement& element) const { + if (!IsHTMLOptionElement(element)) + return false; + Node* parent = element.parentNode(); + if (!parent) + return false; + if (parent == &RootNode()) + return true; + return IsHTMLOptGroupElement(*parent) && parent->parentNode() == &RootNode(); +} + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OPTIONS_COLLECTION_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl new file mode 100644 index 00000000000..2d020678172 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_options_collection.idl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2013, 2014 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. + */ + +// https://html.spec.whatwg.org/#the-htmloptionscollection-interface + +interface HTMLOptionsCollection : HTMLCollection { + // Inherits item() and namedItem() + [CEReactions, RaisesException=Setter] attribute unsigned long length; // shadows inherited length + [CEReactions, RaisesException] setter void (unsigned long index, HTMLOptionElement? option); + [CEReactions, RaisesException] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); + [CEReactions] void remove(long index); + attribute long selectedIndex; + + // TODO(tkent): We need to declare these getters because our IDL compiler + // doesn't support inheritance of anonymous getters. crbug.com/752877 + [ImplementedAs=item] getter Element? (unsigned long index); + [ImplementedAs=namedItem] getter Element? (DOMString name); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc new file mode 100644 index 00000000000..69fa1c1768b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.cc @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/html_output_element.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +inline HTMLOutputElement::HTMLOutputElement(Document& document) + : HTMLFormControlElement(HTMLNames::outputTag, document), + is_default_value_mode_(true), + default_value_(""), + tokens_(DOMTokenList::Create(*this, HTMLNames::forAttr)) {} + +HTMLOutputElement::~HTMLOutputElement() = default; + +HTMLOutputElement* HTMLOutputElement::Create(Document& document) { + return new HTMLOutputElement(document); +} + +const AtomicString& HTMLOutputElement::FormControlType() const { + DEFINE_STATIC_LOCAL(const AtomicString, output, ("output")); + return output; +} + +bool HTMLOutputElement::IsDisabledFormControl() const { + return false; +} + +bool HTMLOutputElement::MatchesEnabledPseudoClass() const { + return false; +} + +bool HTMLOutputElement::SupportsFocus() const { + return HTMLElement::SupportsFocus(); +} + +void HTMLOutputElement::ParseAttribute( + const AttributeModificationParams& params) { + if (params.name == HTMLNames::forAttr) + tokens_->DidUpdateAttributeValue(params.old_value, params.new_value); + else + HTMLFormControlElement::ParseAttribute(params); +} + +DOMTokenList* HTMLOutputElement::htmlFor() const { + return tokens_.Get(); +} + +void HTMLOutputElement::ChildrenChanged(const ChildrenChange& change) { + HTMLFormControlElement::ChildrenChanged(change); + + if (is_default_value_mode_) + default_value_ = textContent(); +} + +void HTMLOutputElement::ResetImpl() { + // The reset algorithm for output elements is to set the element's + // value mode flag to "default" and then to set the element's textContent + // attribute to the default value. + if (default_value_ == value()) + return; + setTextContent(default_value_); + is_default_value_mode_ = true; +} + +String HTMLOutputElement::value() const { + return textContent(); +} + +void HTMLOutputElement::setValue(const String& value) { + // The value mode flag set to "value" when the value attribute is set. + is_default_value_mode_ = false; + if (value == this->value()) + return; + setTextContent(value); +} + +String HTMLOutputElement::defaultValue() const { + return default_value_; +} + +void HTMLOutputElement::setDefaultValue(const String& value) { + if (default_value_ == value) + return; + default_value_ = value; + // The spec requires the value attribute set to the default value + // when the element's value mode flag to "default". + if (is_default_value_mode_) + setTextContent(value); +} + +int HTMLOutputElement::tabIndex() const { + return HTMLElement::tabIndex(); +} + +void HTMLOutputElement::Trace(blink::Visitor* visitor) { + visitor->Trace(tokens_); + HTMLFormControlElement::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.h new file mode 100644 index 00000000000..2c322a0a70f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OUTPUT_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OUTPUT_ELEMENT_H_ + +#include "third_party/blink/renderer/core/dom/dom_token_list.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" + +namespace blink { + +class CORE_EXPORT HTMLOutputElement final : public HTMLFormControlElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLOutputElement* Create(Document&); + ~HTMLOutputElement() override; + + bool willValidate() const override { return false; } + + String value() const; + void setValue(const String&); + String defaultValue() const; + void setDefaultValue(const String&); + DOMTokenList* htmlFor() const; + + bool CanContainRangeEndPoint() const override { + return is_default_value_mode_; + } + + void Trace(blink::Visitor*) override; + + private: + explicit HTMLOutputElement(Document&); + + void ParseAttribute(const AttributeModificationParams&) override; + const AtomicString& FormControlType() const override; + bool IsDisabledFormControl() const override; + bool MatchesEnabledPseudoClass() const override; + bool IsEnumeratable() const override { return true; } + bool SupportLabels() const override { return true; } + bool SupportsFocus() const override; + void ChildrenChanged(const ChildrenChange&) override; + void ResetImpl() override; + int tabIndex() const override; + + bool is_default_value_mode_; + String default_value_; + Member<DOMTokenList> tokens_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_OUTPUT_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl new file mode 100644 index 00000000000..e6471696564 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element.idl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +// https://html.spec.whatwg.org/#the-output-element +[HTMLConstructor] +interface HTMLOutputElement : HTMLElement { + [PutForwards=value] readonly attribute DOMTokenList htmlFor; + [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form; + [CEReactions, Reflect] attribute DOMString name; + + readonly attribute DOMString type; + [CEReactions] attribute DOMString defaultValue; + [CEReactions] attribute DOMString value; + + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); + + readonly attribute NodeList labels; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc new file mode 100644 index 00000000000..fdb5cbe5979 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_output_element_test.cc @@ -0,0 +1,32 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/html_output_element.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/dom_token_list.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +TEST(HTMLLinkElementSizesAttributeTest, + setHTMLForProperty_updatesForAttribute) { + Document* document = Document::CreateForTest(); + HTMLOutputElement* element = HTMLOutputElement::Create(*document); + EXPECT_EQ(g_null_atom, element->getAttribute(HTMLNames::forAttr)); + element->htmlFor()->setValue(" strawberry "); + EXPECT_EQ(" strawberry ", element->getAttribute(HTMLNames::forAttr)); +} + +TEST(HTMLOutputElementTest, setForAttribute_updatesHTMLForPropertyValue) { + Document* document = Document::CreateForTest(); + HTMLOutputElement* element = HTMLOutputElement::Create(*document); + DOMTokenList* for_tokens = element->htmlFor(); + EXPECT_EQ(g_null_atom, for_tokens->value()); + element->setAttribute(HTMLNames::forAttr, "orange grape"); + EXPECT_EQ("orange grape", for_tokens->value()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc new file mode 100644 index 00000000000..66338a64dd8 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc @@ -0,0 +1,2086 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights + * reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * + * 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 "third_party/blink/renderer/core/html/forms/html_select_element.h" + +#include "build/build_config.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/bindings/core/v8/html_element_or_long.h" +#include "third_party/blink/renderer/bindings/core/v8/html_option_element_or_html_opt_group_element.h" +#include "third_party/blink/renderer/core/dom/attribute.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/mutation_observer.h" +#include "third_party/blink/renderer/core/dom/mutation_observer_init.h" +#include "third_party/blink/renderer/core/dom/mutation_record.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/dom/node_lists_node_data.h" +#include "third_party/blink/renderer/core/dom/node_traversal.h" +#include "third_party/blink/renderer/core/events/gesture_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/popup_menu.h" +#include "third_party/blink/renderer/core/html/html_hr_element.h" +#include "third_party/blink/renderer/core/html/html_slot_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/input/input_device_capabilities.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/layout/hit_test_request.h" +#include "third_party/blink/renderer/core/layout/hit_test_result.h" +#include "third_party/blink/renderer/core/layout/layout_list_box.h" +#include "third_party/blink/renderer/core/layout/layout_menu_list.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/autoscroll_controller.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/page/spatial_navigation.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +using namespace HTMLNames; + +// Upper limit of m_listItems. According to the HTML standard, options larger +// than this limit doesn't work well because |selectedIndex| IDL attribute is +// signed. +static const unsigned kMaxListItems = INT_MAX; + +HTMLSelectElement::HTMLSelectElement(Document& document) + : HTMLFormControlElementWithState(selectTag, document), + type_ahead_(this), + size_(0), + last_on_change_option_(nullptr), + is_multiple_(false), + active_selection_state_(false), + should_recalc_list_items_(false), + is_autofilled_by_preview_(false), + index_to_select_on_cancel_(-1), + popup_is_visible_(false) { + SetHasCustomStyleCallbacks(); +} + +HTMLSelectElement* HTMLSelectElement::Create(Document& document) { + HTMLSelectElement* select = new HTMLSelectElement(document); + select->EnsureUserAgentShadowRoot(); + return select; +} + +HTMLSelectElement::~HTMLSelectElement() = default; + +// static +bool HTMLSelectElement::CanAssignToSelectSlot(const Node& node) { + return node.HasTagName(optionTag) || node.HasTagName(optgroupTag) || + node.HasTagName(hrTag); +} + +const AtomicString& HTMLSelectElement::FormControlType() const { + DEFINE_STATIC_LOCAL(const AtomicString, select_multiple, ("select-multiple")); + DEFINE_STATIC_LOCAL(const AtomicString, select_one, ("select-one")); + return is_multiple_ ? select_multiple : select_one; +} + +bool HTMLSelectElement::HasPlaceholderLabelOption() const { + // The select element has no placeholder label option if it has an attribute + // "multiple" specified or a display size of non-1. + // + // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec + // 3, 2010. "size() != 1" is correct. Using "size() > 1" here because + // size() may be 0 in WebKit. See the discussion at + // https://bugs.webkit.org/show_bug.cgi?id=43887 + // + // "0 size()" happens when an attribute "size" is absent or an invalid size + // attribute is specified. In this case, the display size should be assumed + // as the default. The default display size is 1 for non-multiple select + // elements, and 4 for multiple select elements. + // + // Finally, if size() == 0 and non-multiple, the display size can be assumed + // as 1. + if (IsMultiple() || size() > 1) + return false; + + // TODO(tkent): This function is called in CSS selector matching. Using + // listItems() might have performance impact. + if (GetListItems().size() == 0 || !IsHTMLOptionElement(GetListItems()[0])) + return false; + return ToHTMLOptionElement(GetListItems()[0])->value().IsEmpty(); +} + +String HTMLSelectElement::validationMessage() const { + if (!willValidate()) + return String(); + if (CustomError()) + return CustomValidationMessage(); + if (ValueMissing()) { + return GetLocale().QueryString( + WebLocalizedString::kValidationValueMissingForSelect); + } + return String(); +} + +bool HTMLSelectElement::ValueMissing() const { + if (!willValidate()) + return false; + + if (!IsRequired()) + return false; + + int first_selection_index = selectedIndex(); + + // If a non-placeholer label option is selected (firstSelectionIndex > 0), + // it's not value-missing. + return first_selection_index < 0 || + (!first_selection_index && HasPlaceholderLabelOption()); +} + +String HTMLSelectElement::DefaultToolTip() const { + if (Form() && Form()->NoValidate()) + return String(); + return validationMessage(); +} + +void HTMLSelectElement::SelectMultipleOptionsByPopup( + const Vector<int>& list_indices) { + DCHECK(UsesMenuList()); + DCHECK(IsMultiple()); + for (size_t i = 0; i < list_indices.size(); ++i) { + bool add_selection_if_not_first = i > 0; + if (HTMLOptionElement* option = OptionAtListIndex(list_indices[i])) + UpdateSelectedState(option, add_selection_if_not_first, false); + } + SetNeedsValidityCheck(); + // TODO(tkent): Using listBoxOnChange() is very confusing. + ListBoxOnChange(); +} + +bool HTMLSelectElement::UsesMenuList() const { + if (LayoutTheme::GetTheme().DelegatesMenuListRendering()) + return true; + + return !is_multiple_ && size_ <= 1; +} + +int HTMLSelectElement::ActiveSelectionEndListIndex() const { + HTMLOptionElement* option = ActiveSelectionEnd(); + return option ? option->ListIndex() : -1; +} + +HTMLOptionElement* HTMLSelectElement::ActiveSelectionEnd() const { + if (active_selection_end_) + return active_selection_end_.Get(); + return LastSelectedOption(); +} + +void HTMLSelectElement::add( + const HTMLOptionElementOrHTMLOptGroupElement& element, + const HTMLElementOrLong& before, + ExceptionState& exception_state) { + HTMLElement* element_to_insert; + DCHECK(!element.IsNull()); + if (element.IsHTMLOptionElement()) + element_to_insert = element.GetAsHTMLOptionElement(); + else + element_to_insert = element.GetAsHTMLOptGroupElement(); + + HTMLElement* before_element; + if (before.IsHTMLElement()) + before_element = before.GetAsHTMLElement(); + else if (before.IsLong()) + before_element = options()->item(before.GetAsLong()); + else + before_element = nullptr; + + InsertBefore(element_to_insert, before_element, exception_state); + SetNeedsValidityCheck(); +} + +void HTMLSelectElement::remove(int option_index) { + if (HTMLOptionElement* option = item(option_index)) + option->remove(IGNORE_EXCEPTION_FOR_TESTING); +} + +String HTMLSelectElement::value() const { + if (HTMLOptionElement* option = SelectedOption()) + return option->value(); + return ""; +} + +void HTMLSelectElement::setValue(const String& value, bool send_events) { + HTMLOptionElement* option = nullptr; + // Find the option with value() matching the given parameter and make it the + // current selection. + for (auto* const item : GetOptionList()) { + if (item->value() == value) { + option = item; + break; + } + } + + HTMLOptionElement* previous_selected_option = SelectedOption(); + SetSuggestedOption(nullptr); + if (is_autofilled_by_preview_) + SetAutofilled(false); + SelectOptionFlags flags = kDeselectOtherOptions | kMakeOptionDirty; + if (send_events) + flags |= kDispatchInputAndChangeEvent; + SelectOption(option, flags); + + if (send_events && previous_selected_option != option && !UsesMenuList()) + ListBoxOnChange(); +} + +String HTMLSelectElement::SuggestedValue() const { + return suggested_option_ ? suggested_option_->value() : ""; +} + +void HTMLSelectElement::SetSuggestedValue(const String& value) { + if (value.IsNull()) { + SetSuggestedOption(nullptr); + return; + } + + for (auto* const option : GetOptionList()) { + if (option->value() == value) { + SetSuggestedOption(option); + is_autofilled_by_preview_ = true; + return; + } + } + + SetSuggestedOption(nullptr); +} + +bool HTMLSelectElement::IsPresentationAttribute( + const QualifiedName& name) const { + if (name == alignAttr) { + // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. + // See http://bugs.webkit.org/show_bug.cgi?id=12072 + return false; + } + + return HTMLFormControlElementWithState::IsPresentationAttribute(name); +} + +void HTMLSelectElement::ParseAttribute( + const AttributeModificationParams& params) { + if (params.name == sizeAttr) { + unsigned old_size = size_; + if (!ParseHTMLNonNegativeInteger(params.new_value, size_)) + size_ = 0; + SetNeedsValidityCheck(); + if (size_ != old_size) { + if (InActiveDocument()) + LazyReattachIfAttached(); + ResetToDefaultSelection(); + if (!UsesMenuList()) + SaveListboxActiveSelection(); + } + } else if (params.name == multipleAttr) { + ParseMultipleAttribute(params.new_value); + } else if (params.name == accesskeyAttr) { + // FIXME: ignore for the moment. + // + } else { + HTMLFormControlElementWithState::ParseAttribute(params); + } +} + +bool HTMLSelectElement::ShouldShowFocusRingOnMouseFocus() const { + return true; +} + +bool HTMLSelectElement::CanSelectAll() const { + return !UsesMenuList(); +} + +LayoutObject* HTMLSelectElement::CreateLayoutObject(const ComputedStyle&) { + if (UsesMenuList()) + return new LayoutMenuList(this); + return new LayoutListBox(this); +} + +HTMLCollection* HTMLSelectElement::selectedOptions() { + return EnsureCachedCollection<HTMLCollection>(kSelectedOptions); +} + +HTMLOptionsCollection* HTMLSelectElement::options() { + return EnsureCachedCollection<HTMLOptionsCollection>(kSelectOptions); +} + +void HTMLSelectElement::OptionElementChildrenChanged( + const HTMLOptionElement& option) { + SetNeedsValidityCheck(); + + if (GetLayoutObject()) { + if (option.Selected() && UsesMenuList()) + GetLayoutObject()->UpdateFromElement(); + if (AXObjectCache* cache = + GetLayoutObject()->GetDocument().ExistingAXObjectCache()) + cache->ChildrenChanged(this); + } +} + +void HTMLSelectElement::AccessKeyAction(bool send_mouse_events) { + focus(); + DispatchSimulatedClick( + nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents); +} + +Element* HTMLSelectElement::namedItem(const AtomicString& name) { + return options()->namedItem(name); +} + +HTMLOptionElement* HTMLSelectElement::item(unsigned index) { + return options()->item(index); +} + +void HTMLSelectElement::SetOption(unsigned index, + HTMLOptionElement* option, + ExceptionState& exception_state) { + int diff = index - length(); + // We should check |index >= maxListItems| first to avoid integer overflow. + if (index >= kMaxListItems || + GetListItems().size() + diff + 1 > kMaxListItems) { + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kWarningMessageLevel, + String::Format("Blocked to expand the option list and set an option at " + "index=%u. The maximum list length is %u.", + index, kMaxListItems))); + return; + } + HTMLOptionElementOrHTMLOptGroupElement element; + element.SetHTMLOptionElement(option); + HTMLElementOrLong before; + // Out of array bounds? First insert empty dummies. + if (diff > 0) { + setLength(index, exception_state); + // Replace an existing entry? + } else if (diff < 0) { + before.SetHTMLElement(options()->item(index + 1)); + remove(index); + } + if (exception_state.HadException()) + return; + // Finally add the new element. + EventQueueScope scope; + add(element, before, exception_state); + if (diff >= 0 && option->Selected()) + OptionSelectionStateChanged(option, true); +} + +void HTMLSelectElement::setLength(unsigned new_len, + ExceptionState& exception_state) { + // We should check |newLen > maxListItems| first to avoid integer overflow. + if (new_len > kMaxListItems || + GetListItems().size() + new_len - length() > kMaxListItems) { + GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kJSMessageSource, kWarningMessageLevel, + String::Format("Blocked to expand the option list to %u items. The " + "maximum list length is %u.", + new_len, kMaxListItems))); + return; + } + int diff = length() - new_len; + + if (diff < 0) { // Add dummy elements. + do { + AppendChild(HTMLOptionElement::Create(GetDocument()), exception_state); + if (exception_state.HadException()) + break; + } while (++diff); + } else { + // Removing children fires mutation events, which might mutate the DOM + // further, so we first copy out a list of elements that we intend to + // remove then attempt to remove them one at a time. + HeapVector<Member<HTMLOptionElement>> items_to_remove; + size_t option_index = 0; + for (auto* const option : GetOptionList()) { + if (option_index++ >= new_len) { + DCHECK(option->parentNode()); + items_to_remove.push_back(option); + } + } + + for (auto& item : items_to_remove) { + if (item->parentNode()) + item->parentNode()->RemoveChild(item.Get(), exception_state); + } + } + SetNeedsValidityCheck(); +} + +bool HTMLSelectElement::IsRequiredFormControl() const { + return IsRequired(); +} + +HTMLOptionElement* HTMLSelectElement::OptionAtListIndex(int list_index) const { + if (list_index < 0) + return nullptr; + const ListItems& items = GetListItems(); + if (static_cast<size_t>(list_index) >= items.size()) + return nullptr; + return ToHTMLOptionElementOrNull(items[list_index]); +} + +// Returns the 1st valid OPTION |skip| items from |listIndex| in direction +// |direction| if there is one. +// Otherwise, it returns the valid OPTION closest to that boundary which is past +// |listIndex| if there is one. +// Otherwise, it returns nullptr. +// Valid means that it is enabled and visible. +HTMLOptionElement* HTMLSelectElement::NextValidOption(int list_index, + SkipDirection direction, + int skip) const { + DCHECK(direction == kSkipBackwards || direction == kSkipForwards); + const ListItems& list_items = GetListItems(); + HTMLOptionElement* last_good_option = nullptr; + int size = list_items.size(); + for (list_index += direction; list_index >= 0 && list_index < size; + list_index += direction) { + --skip; + HTMLElement* element = list_items[list_index]; + if (!IsHTMLOptionElement(*element)) + continue; + if (ToHTMLOptionElement(*element).IsDisplayNone()) + continue; + if (element->IsDisabledFormControl()) + continue; + if (!UsesMenuList() && !element->GetLayoutObject()) + continue; + last_good_option = ToHTMLOptionElement(element); + if (skip <= 0) + break; + } + return last_good_option; +} + +HTMLOptionElement* HTMLSelectElement::NextSelectableOption( + HTMLOptionElement* start_option) const { + return NextValidOption(start_option ? start_option->ListIndex() : -1, + kSkipForwards, 1); +} + +HTMLOptionElement* HTMLSelectElement::PreviousSelectableOption( + HTMLOptionElement* start_option) const { + return NextValidOption( + start_option ? start_option->ListIndex() : GetListItems().size(), + kSkipBackwards, 1); +} + +HTMLOptionElement* HTMLSelectElement::FirstSelectableOption() const { + // TODO(tkent): This is not efficient. nextSlectableOption(nullptr) is + // faster. + return NextValidOption(GetListItems().size(), kSkipBackwards, INT_MAX); +} + +HTMLOptionElement* HTMLSelectElement::LastSelectableOption() const { + // TODO(tkent): This is not efficient. previousSlectableOption(nullptr) is + // faster. + return NextValidOption(-1, kSkipForwards, INT_MAX); +} + +// Returns the index of the next valid item one page away from |startIndex| in +// direction |direction|. +HTMLOptionElement* HTMLSelectElement::NextSelectableOptionPageAway( + HTMLOptionElement* start_option, + SkipDirection direction) const { + const ListItems& items = GetListItems(); + // Can't use m_size because layoutObject forces a minimum size. + int page_size = 0; + if (GetLayoutObject()->IsListBox()) { + // -1 so we still show context. + page_size = ToLayoutListBox(GetLayoutObject())->size() - 1; + } + + // One page away, but not outside valid bounds. + // If there is a valid option item one page away, the index is chosen. + // If there is no exact one page away valid option, returns startIndex or + // the most far index. + int start_index = start_option ? start_option->ListIndex() : -1; + int edge_index = (direction == kSkipForwards) ? 0 : (items.size() - 1); + int skip_amount = + page_size + + ((direction == kSkipForwards) ? start_index : (edge_index - start_index)); + return NextValidOption(edge_index, direction, skip_amount); +} + +void HTMLSelectElement::SelectAll() { + DCHECK(!UsesMenuList()); + if (!GetLayoutObject() || !is_multiple_) + return; + + // Save the selection so it can be compared to the new selectAll selection + // when dispatching change events. + SaveLastSelection(); + + active_selection_state_ = true; + SetActiveSelectionAnchor(NextSelectableOption(nullptr)); + SetActiveSelectionEnd(PreviousSelectableOption(nullptr)); + + UpdateListBoxSelection(false, false); + ListBoxOnChange(); + SetNeedsValidityCheck(); +} + +void HTMLSelectElement::SaveLastSelection() { + if (UsesMenuList()) { + last_on_change_option_ = SelectedOption(); + return; + } + + last_on_change_selection_.clear(); + for (auto& element : GetListItems()) { + last_on_change_selection_.push_back( + IsHTMLOptionElement(*element) && + ToHTMLOptionElement(element)->Selected()); + } +} + +void HTMLSelectElement::SetActiveSelectionAnchor(HTMLOptionElement* option) { + active_selection_anchor_ = option; + if (!UsesMenuList()) + SaveListboxActiveSelection(); +} + +void HTMLSelectElement::SaveListboxActiveSelection() { + // Cache the selection state so we can restore the old selection as the new + // selection pivots around this anchor index. + // Example: + // 1. Press the mouse button on the second OPTION + // m_activeSelectionAnchorIndex = 1 + // 2. Drag the mouse pointer onto the fifth OPTION + // m_activeSelectionEndIndex = 4, options at 1-4 indices are selected. + // 3. Drag the mouse pointer onto the fourth OPTION + // m_activeSelectionEndIndex = 3, options at 1-3 indices are selected. + // updateListBoxSelection needs to clear selection of the fifth OPTION. + cached_state_for_active_selection_.resize(0); + for (auto* const option : GetOptionList()) { + cached_state_for_active_selection_.push_back(option->Selected()); + } +} + +void HTMLSelectElement::SetActiveSelectionEnd(HTMLOptionElement* option) { + active_selection_end_ = option; +} + +void HTMLSelectElement::UpdateListBoxSelection(bool deselect_other_options, + bool scroll) { + DCHECK(GetLayoutObject()); + DCHECK(GetLayoutObject()->IsListBox() || is_multiple_); + + int active_selection_anchor_index = + active_selection_anchor_ ? active_selection_anchor_->index() : -1; + int active_selection_end_index = + active_selection_end_ ? active_selection_end_->index() : -1; + int start = + std::min(active_selection_anchor_index, active_selection_end_index); + int end = std::max(active_selection_anchor_index, active_selection_end_index); + + int i = 0; + for (auto* const option : GetOptionList()) { + if (option->IsDisabledFormControl() || !option->GetLayoutObject()) { + ++i; + continue; + } + if (i >= start && i <= end) { + option->SetSelectedState(active_selection_state_); + option->SetDirty(true); + } else if (deselect_other_options || + i >= static_cast<int>( + cached_state_for_active_selection_.size())) { + option->SetSelectedState(false); + option->SetDirty(true); + } else { + option->SetSelectedState(cached_state_for_active_selection_[i]); + } + ++i; + } + + SetNeedsValidityCheck(); + if (scroll) + ScrollToSelection(); + NotifyFormStateChanged(); +} + +void HTMLSelectElement::ListBoxOnChange() { + DCHECK(!UsesMenuList() || is_multiple_); + + const ListItems& items = GetListItems(); + + // If the cached selection list is empty, or the size has changed, then fire + // dispatchFormControlChangeEvent, and return early. + // FIXME: Why? This looks unreasonable. + if (last_on_change_selection_.IsEmpty() || + last_on_change_selection_.size() != items.size()) { + DispatchChangeEvent(); + return; + } + + // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent. + bool fire_on_change = false; + for (unsigned i = 0; i < items.size(); ++i) { + HTMLElement* element = items[i]; + bool selected = IsHTMLOptionElement(*element) && + ToHTMLOptionElement(element)->Selected(); + if (selected != last_on_change_selection_[i]) + fire_on_change = true; + last_on_change_selection_[i] = selected; + } + + if (fire_on_change) { + DispatchInputEvent(); + DispatchChangeEvent(); + } +} + +void HTMLSelectElement::DispatchInputAndChangeEventForMenuList() { + DCHECK(UsesMenuList()); + + HTMLOptionElement* selected_option = SelectedOption(); + if (last_on_change_option_.Get() != selected_option) { + last_on_change_option_ = selected_option; + DispatchInputEvent(); + DispatchChangeEvent(); + } +} + +void HTMLSelectElement::ScrollToSelection() { + if (!IsFinishedParsingChildren()) + return; + if (UsesMenuList()) + return; + ScrollToOption(ActiveSelectionEnd()); + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) + cache->ListboxActiveIndexChanged(this); +} + +void HTMLSelectElement::SetOptionsChangedOnLayoutObject() { + if (LayoutObject* layout_object = GetLayoutObject()) { + if (!UsesMenuList()) + return; + ToLayoutMenuList(layout_object) + ->SetNeedsLayoutAndPrefWidthsRecalc( + LayoutInvalidationReason::kMenuOptionsChanged); + } +} + +const HTMLSelectElement::ListItems& HTMLSelectElement::GetListItems() const { + if (should_recalc_list_items_) { + RecalcListItems(); + } else { +#if DCHECK_IS_ON() + HeapVector<Member<HTMLElement>> items = list_items_; + RecalcListItems(); + DCHECK(items == list_items_); +#endif + } + + return list_items_; +} + +void HTMLSelectElement::InvalidateSelectedItems() { + if (HTMLCollection* collection = + CachedCollection<HTMLCollection>(kSelectedOptions)) + collection->InvalidateCache(); +} + +void HTMLSelectElement::SetRecalcListItems() { + // FIXME: This function does a bunch of confusing things depending on if it + // is in the document or not. + + should_recalc_list_items_ = true; + + SetOptionsChangedOnLayoutObject(); + if (!isConnected()) { + if (HTMLOptionsCollection* collection = + CachedCollection<HTMLOptionsCollection>(kSelectOptions)) + collection->InvalidateCache(); + InvalidateSelectedItems(); + } + + if (GetLayoutObject()) { + if (AXObjectCache* cache = + GetLayoutObject()->GetDocument().ExistingAXObjectCache()) + cache->ChildrenChanged(this); + } +} + +void HTMLSelectElement::RecalcListItems() const { + TRACE_EVENT0("blink", "HTMLSelectElement::recalcListItems"); + list_items_.resize(0); + + should_recalc_list_items_ = false; + + for (Element* current_element = ElementTraversal::FirstWithin(*this); + current_element && list_items_.size() < kMaxListItems;) { + if (!current_element->IsHTMLElement()) { + current_element = + ElementTraversal::NextSkippingChildren(*current_element, this); + continue; + } + HTMLElement& current = ToHTMLElement(*current_element); + + // We should ignore nested optgroup elements. The HTML parser flatten + // them. However we need to ignore nested optgroups built by DOM APIs. + // This behavior matches to IE and Firefox. + if (IsHTMLOptGroupElement(current)) { + if (current.parentNode() != this) { + current_element = ElementTraversal::NextSkippingChildren(current, this); + continue; + } + list_items_.push_back(¤t); + if (Element* next_element = ElementTraversal::FirstWithin(current)) { + current_element = next_element; + continue; + } + } + + if (IsHTMLOptionElement(current)) + list_items_.push_back(¤t); + + if (IsHTMLHRElement(current)) + list_items_.push_back(¤t); + + // In conforming HTML code, only <optgroup> and <option> will be found + // within a <select>. We call NodeTraversal::nextSkippingChildren so + // that we only step into those tags that we choose to. For web-compat, + // we should cope with the case where odd tags like a <div> have been + // added but we handle this because such tags have already been removed + // from the <select>'s subtree at this point. + current_element = + ElementTraversal::NextSkippingChildren(*current_element, this); + } +} + +void HTMLSelectElement::ResetToDefaultSelection(ResetReason reason) { + // https://html.spec.whatwg.org/multipage/forms.html#ask-for-a-reset + if (IsMultiple()) + return; + HTMLOptionElement* first_enabled_option = nullptr; + HTMLOptionElement* last_selected_option = nullptr; + bool did_change = false; + int option_index = 0; + // We can't use HTMLSelectElement::options here because this function is + // called in Node::insertedInto and Node::removedFrom before invalidating + // node collections. + for (auto* const option : GetOptionList()) { + if (option->Selected()) { + if (last_selected_option) { + last_selected_option->SetSelectedState(false); + did_change = true; + } + last_selected_option = option; + } + if (!first_enabled_option && !option->IsDisabledFormControl()) { + first_enabled_option = option; + if (reason == kResetReasonSelectedOptionRemoved) { + // There must be no selected OPTIONs. + break; + } + } + ++option_index; + } + if (!last_selected_option && size_ <= 1 && + (!first_enabled_option || + (first_enabled_option && !first_enabled_option->Selected()))) { + SelectOption(first_enabled_option, + reason == kResetReasonSelectedOptionRemoved + ? 0 + : kDeselectOtherOptions); + last_selected_option = first_enabled_option; + did_change = true; + } + if (did_change) + SetNeedsValidityCheck(); + last_on_change_option_ = last_selected_option; +} + +HTMLOptionElement* HTMLSelectElement::SelectedOption() const { + for (auto* const option : GetOptionList()) { + if (option->Selected()) + return option; + } + return nullptr; +} + +int HTMLSelectElement::selectedIndex() const { + unsigned index = 0; + + // Return the number of the first option selected. + for (auto* const option : GetOptionList()) { + if (option->Selected()) + return index; + ++index; + } + + return -1; +} + +void HTMLSelectElement::setSelectedIndex(int index) { + SelectOption(item(index), kDeselectOtherOptions | kMakeOptionDirty); +} + +int HTMLSelectElement::SelectedListIndex() const { + int index = 0; + for (const auto& item : GetListItems()) { + if (IsHTMLOptionElement(item) && ToHTMLOptionElement(item)->Selected()) + return index; + ++index; + } + return -1; +} + +void HTMLSelectElement::SetSuggestedOption(HTMLOptionElement* option) { + if (suggested_option_ == option) + return; + suggested_option_ = option; + + if (LayoutObject* layout_object = GetLayoutObject()) { + layout_object->UpdateFromElement(); + ScrollToOption(option); + } + if (PopupIsVisible()) + popup_->UpdateFromElement(PopupMenu::kBySelectionChange); +} + +void HTMLSelectElement::ScrollToOption(HTMLOptionElement* option) { + if (!option) + return; + if (UsesMenuList()) + return; + bool has_pending_task = option_to_scroll_to_; + // We'd like to keep an HTMLOptionElement reference rather than the index of + // the option because the task should work even if unselected option is + // inserted before executing scrollToOptionTask(). + option_to_scroll_to_ = option; + if (!has_pending_task) { + GetDocument() + .GetTaskRunner(TaskType::kUserInteraction) + ->PostTask(FROM_HERE, WTF::Bind(&HTMLSelectElement::ScrollToOptionTask, + WrapPersistent(this))); + } +} + +void HTMLSelectElement::ScrollToOptionTask() { + HTMLOptionElement* option = option_to_scroll_to_.Release(); + if (!option || !isConnected()) + return; + // optionRemoved() makes sure m_optionToScrollTo doesn't have an option with + // another owner. + DCHECK_EQ(option->OwnerSelectElement(), this); + GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + if (!GetLayoutObject() || !GetLayoutObject()->IsListBox()) + return; + LayoutRect bounds = option->BoundingBoxForScrollIntoView(); + ToLayoutListBox(GetLayoutObject())->ScrollToRect(bounds); +} + +void HTMLSelectElement::OptionSelectionStateChanged(HTMLOptionElement* option, + bool option_is_selected) { + DCHECK_EQ(option->OwnerSelectElement(), this); + if (option_is_selected) + SelectOption(option, IsMultiple() ? 0 : kDeselectOtherOptions); + else if (!UsesMenuList() || IsMultiple()) + SelectOption(nullptr, IsMultiple() ? 0 : kDeselectOtherOptions); + else + ResetToDefaultSelection(); +} + +void HTMLSelectElement::OptionInserted(HTMLOptionElement& option, + bool option_is_selected) { + DCHECK_EQ(option.OwnerSelectElement(), this); + SetRecalcListItems(); + if (option_is_selected) { + SelectOption(&option, IsMultiple() ? 0 : kDeselectOtherOptions); + } else { + // No need to reset if we already have a selected option. + if (!last_on_change_option_) + ResetToDefaultSelection(); + } + SetNeedsValidityCheck(); + last_on_change_selection_.clear(); + + if (!GetDocument().IsActive()) + return; + + GetDocument() + .GetFrame() + ->GetPage() + ->GetChromeClient() + .SelectFieldOptionsChanged(*this); +} + +void HTMLSelectElement::OptionRemoved(HTMLOptionElement& option) { + SetRecalcListItems(); + if (option.Selected()) + ResetToDefaultSelection(kResetReasonSelectedOptionRemoved); + else if (!last_on_change_option_) + ResetToDefaultSelection(); + if (last_on_change_option_ == &option) + last_on_change_option_.Clear(); + if (option_to_scroll_to_ == &option) + option_to_scroll_to_.Clear(); + if (active_selection_anchor_ == &option) + active_selection_anchor_.Clear(); + if (active_selection_end_ == &option) + active_selection_end_.Clear(); + if (suggested_option_ == &option) + SetSuggestedOption(nullptr); + if (option.Selected()) + SetAutofilled(false); + SetNeedsValidityCheck(); + last_on_change_selection_.clear(); + + if (!GetDocument().IsActive()) + return; + + GetDocument() + .GetFrame() + ->GetPage() + ->GetChromeClient() + .SelectFieldOptionsChanged(*this); +} + +void HTMLSelectElement::OptGroupInsertedOrRemoved( + HTMLOptGroupElement& optgroup) { + SetRecalcListItems(); + SetNeedsValidityCheck(); + last_on_change_selection_.clear(); +} + +void HTMLSelectElement::HrInsertedOrRemoved(HTMLHRElement& hr) { + SetRecalcListItems(); + last_on_change_selection_.clear(); +} + +// TODO(tkent): This function is not efficient. It contains multiple O(N) +// operations. crbug.com/577989. +void HTMLSelectElement::SelectOption(HTMLOptionElement* element, + SelectOptionFlags flags) { + TRACE_EVENT0("blink", "HTMLSelectElement::selectOption"); + + bool should_update_popup = false; + + // selectedOption() is O(N). + if (IsAutofilled() && SelectedOption() != element) + SetAutofilled(false); + + if (element) { + if (!element->Selected()) + should_update_popup = true; + element->SetSelectedState(true); + if (flags & kMakeOptionDirty) + element->SetDirty(true); + } + + // deselectItemsWithoutValidation() is O(N). + if (flags & kDeselectOtherOptions) + should_update_popup |= DeselectItemsWithoutValidation(element); + + // We should update active selection after finishing OPTION state change + // because setActiveSelectionAnchorIndex() stores OPTION's selection state. + if (element) { + // setActiveSelectionAnchor is O(N). + if (!active_selection_anchor_ || !IsMultiple() || + flags & kDeselectOtherOptions) + SetActiveSelectionAnchor(element); + if (!active_selection_end_ || !IsMultiple() || + flags & kDeselectOtherOptions) + SetActiveSelectionEnd(element); + } + + // Need to update m_lastOnChangeOption before + // LayoutMenuList::updateFromElement. + bool should_dispatch_events = false; + if (UsesMenuList()) { + should_dispatch_events = (flags & kDispatchInputAndChangeEvent) && + last_on_change_option_ != element; + last_on_change_option_ = element; + } + + // For the menu list case, this is what makes the selected element appear. + if (LayoutObject* layout_object = GetLayoutObject()) + layout_object->UpdateFromElement(); + // PopupMenu::updateFromElement() posts an O(N) task. + if (PopupIsVisible() && should_update_popup) + popup_->UpdateFromElement(PopupMenu::kBySelectionChange); + + ScrollToSelection(); + SetNeedsValidityCheck(); + + if (UsesMenuList()) { + if (should_dispatch_events) { + DispatchInputEvent(); + DispatchChangeEvent(); + } + if (LayoutObject* layout_object = GetLayoutObject()) { + // Need to check usesMenuList() again because event handlers might + // change the status. + if (UsesMenuList()) { + // didSelectOption() is O(N) because of HTMLOptionElement::index(). + ToLayoutMenuList(layout_object)->DidSelectOption(element); + } + } + } + + NotifyFormStateChanged(); + + if (Frame::HasTransientUserActivation(GetDocument().GetFrame()) && + GetDocument().IsActive()) { + GetDocument() + .GetPage() + ->GetChromeClient() + .DidChangeSelectionInSelectControl(*this); + } +} + +void HTMLSelectElement::DispatchFocusEvent( + Element* old_focused_element, + WebFocusType type, + InputDeviceCapabilities* source_capabilities) { + // Save the selection so it can be compared to the new selection when + // dispatching change events during blur event dispatch. + if (UsesMenuList()) + SaveLastSelection(); + HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type, + source_capabilities); +} + +void HTMLSelectElement::DispatchBlurEvent( + Element* new_focused_element, + WebFocusType type, + InputDeviceCapabilities* source_capabilities) { + type_ahead_.ResetSession(); + // We only need to fire change events here for menu lists, because we fire + // change events for list boxes whenever the selection change is actually + // made. This matches other browsers' behavior. + if (UsesMenuList()) + DispatchInputAndChangeEventForMenuList(); + last_on_change_selection_.clear(); + if (PopupIsVisible()) + HidePopup(); + HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type, + source_capabilities); +} + +// Returns true if selection state of any OPTIONs is changed. +bool HTMLSelectElement::DeselectItemsWithoutValidation( + HTMLOptionElement* exclude_element) { + if (!IsMultiple() && UsesMenuList() && last_on_change_option_ && + last_on_change_option_ != exclude_element) { + last_on_change_option_->SetSelectedState(false); + return true; + } + bool did_update_selection = false; + for (auto* const option : GetOptionList()) { + if (option != exclude_element) { + if (option->Selected()) + did_update_selection = true; + option->SetSelectedState(false); + } + } + return did_update_selection; +} + +FormControlState HTMLSelectElement::SaveFormControlState() const { + const ListItems& items = GetListItems(); + size_t length = items.size(); + FormControlState state; + for (unsigned i = 0; i < length; ++i) { + if (!IsHTMLOptionElement(*items[i])) + continue; + HTMLOptionElement* option = ToHTMLOptionElement(items[i]); + if (!option->Selected()) + continue; + state.Append(option->value()); + state.Append(String::Number(i)); + if (!IsMultiple()) + break; + } + return state; +} + +size_t HTMLSelectElement::SearchOptionsForValue(const String& value, + size_t list_index_start, + size_t list_index_end) const { + const ListItems& items = GetListItems(); + size_t loop_end_index = std::min(items.size(), list_index_end); + for (size_t i = list_index_start; i < loop_end_index; ++i) { + if (!IsHTMLOptionElement(items[i])) + continue; + if (ToHTMLOptionElement(items[i])->value() == value) + return i; + } + return kNotFound; +} + +void HTMLSelectElement::RestoreFormControlState(const FormControlState& state) { + RecalcListItems(); + + const ListItems& items = GetListItems(); + size_t items_size = items.size(); + if (items_size == 0) + return; + + SelectOption(nullptr, kDeselectOtherOptions); + + // The saved state should have at least one value and an index. + DCHECK_GE(state.ValueSize(), 2u); + if (!IsMultiple()) { + size_t index = state[1].ToUInt(); + if (index < items_size && IsHTMLOptionElement(items[index]) && + ToHTMLOptionElement(items[index])->value() == state[0]) { + ToHTMLOptionElement(items[index])->SetSelectedState(true); + ToHTMLOptionElement(items[index])->SetDirty(true); + last_on_change_option_ = ToHTMLOptionElement(items[index]); + } else { + size_t found_index = SearchOptionsForValue(state[0], 0, items_size); + if (found_index != kNotFound) { + ToHTMLOptionElement(items[found_index])->SetSelectedState(true); + ToHTMLOptionElement(items[found_index])->SetDirty(true); + last_on_change_option_ = ToHTMLOptionElement(items[found_index]); + } + } + } else { + size_t start_index = 0; + for (size_t i = 0; i < state.ValueSize(); i += 2) { + const String& value = state[i]; + const size_t index = state[i + 1].ToUInt(); + if (index < items_size && IsHTMLOptionElement(items[index]) && + ToHTMLOptionElement(items[index])->value() == value) { + ToHTMLOptionElement(items[index])->SetSelectedState(true); + ToHTMLOptionElement(items[index])->SetDirty(true); + start_index = index + 1; + } else { + size_t found_index = + SearchOptionsForValue(value, start_index, items_size); + if (found_index == kNotFound) + found_index = SearchOptionsForValue(value, 0, start_index); + if (found_index == kNotFound) + continue; + ToHTMLOptionElement(items[found_index])->SetSelectedState(true); + ToHTMLOptionElement(items[found_index])->SetDirty(true); + start_index = found_index + 1; + } + } + } + + SetNeedsValidityCheck(); +} + +void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) { + bool old_multiple = is_multiple_; + HTMLOptionElement* old_selected_option = SelectedOption(); + is_multiple_ = !value.IsNull(); + SetNeedsValidityCheck(); + LazyReattachIfAttached(); + // Restore selectedIndex after changing the multiple flag to preserve + // selection as single-line and multi-line has different defaults. + if (old_multiple != is_multiple_) { + // Preserving the first selection is compatible with Firefox and + // WebKit. However Edge seems to "ask for a reset" simply. As of 2016 + // March, the HTML specification says nothing about this. + if (old_selected_option) + SelectOption(old_selected_option, kDeselectOtherOptions); + else + ResetToDefaultSelection(); + } +} + +void HTMLSelectElement::AppendToFormData(FormData& form_data) { + const AtomicString& name = GetName(); + if (name.IsEmpty()) + return; + + for (auto* const option : GetOptionList()) { + if (option->Selected() && !option->IsDisabledFormControl()) + form_data.append(name, option->value()); + } +} + +void HTMLSelectElement::ResetImpl() { + for (auto* const option : GetOptionList()) { + option->SetSelectedState(option->FastHasAttribute(selectedAttr)); + option->SetDirty(false); + } + ResetToDefaultSelection(); + SetNeedsValidityCheck(); +} + +void HTMLSelectElement::HandlePopupOpenKeyboardEvent(Event* event) { + focus(); + // Calling focus() may cause us to lose our layoutObject. Return true so + // that our caller doesn't process the event further, but don't set + // the event as handled. + if (!GetLayoutObject() || !GetLayoutObject()->IsMenuList() || + IsDisabledFormControl()) + return; + // Save the selection so it can be compared to the new selection when + // dispatching change events during selectOption, which gets called from + // selectOptionByPopup, which gets called after the user makes a selection + // from the menu. + SaveLastSelection(); + ShowPopup(); + event->SetDefaultHandled(); + return; +} + +bool HTMLSelectElement::ShouldOpenPopupForKeyDownEvent( + KeyboardEvent* key_event) { + const String& key = key_event->key(); + LayoutTheme& layout_theme = LayoutTheme::GetTheme(); + + if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) + return false; + + return ((layout_theme.PopsMenuByArrowKeys() && + (key == "ArrowDown" || key == "ArrowUp")) || + (layout_theme.PopsMenuByAltDownUpOrF4Key() && + (key == "ArrowDown" || key == "ArrowUp") && key_event->altKey()) || + (layout_theme.PopsMenuByAltDownUpOrF4Key() && + (!key_event->altKey() && !key_event->ctrlKey() && key == "F4"))); +} + +bool HTMLSelectElement::ShouldOpenPopupForKeyPressEvent(KeyboardEvent* event) { + LayoutTheme& layout_theme = LayoutTheme::GetTheme(); + int key_code = event->keyCode(); + + return ((layout_theme.PopsMenuBySpaceKey() && event->keyCode() == ' ' && + !type_ahead_.HasActiveSession(event)) || + (layout_theme.PopsMenuByReturnKey() && key_code == '\r')); +} + +void HTMLSelectElement::MenuListDefaultEventHandler(Event* event) { + if (event->type() == EventTypeNames::keydown) { + if (!GetLayoutObject() || !event->IsKeyboardEvent()) + return; + + KeyboardEvent* key_event = ToKeyboardEvent(event); + if (ShouldOpenPopupForKeyDownEvent(key_event)) { + HandlePopupOpenKeyboardEvent(event); + return; + } + + // When using spatial navigation, we want to be able to navigate away + // from the select element when the user hits any of the arrow keys, + // instead of changing the selection. + if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) { + if (!active_selection_state_) + return; + } + + // The key handling below shouldn't be used for non spatial navigation + // mode Mac + if (LayoutTheme::GetTheme().PopsMenuByArrowKeys() && + !IsSpatialNavigationEnabled(GetDocument().GetFrame())) + return; + + int ignore_modifiers = WebInputEvent::kShiftKey | + WebInputEvent::kControlKey | WebInputEvent::kAltKey | + WebInputEvent::kMetaKey; + if (key_event->GetModifiers() & ignore_modifiers) + return; + + const String& key = key_event->key(); + bool handled = true; + const ListItems& list_items = GetListItems(); + HTMLOptionElement* option = SelectedOption(); + int list_index = option ? option->ListIndex() : -1; + + if (key == "ArrowDown" || key == "ArrowRight") + option = NextValidOption(list_index, kSkipForwards, 1); + else if (key == "ArrowUp" || key == "ArrowLeft") + option = NextValidOption(list_index, kSkipBackwards, 1); + else if (key == "PageDown") + option = NextValidOption(list_index, kSkipForwards, 3); + else if (key == "PageUp") + option = NextValidOption(list_index, kSkipBackwards, 3); + else if (key == "Home") + option = NextValidOption(-1, kSkipForwards, 1); + else if (key == "End") + option = NextValidOption(list_items.size(), kSkipBackwards, 1); + else + handled = false; + + if (handled && option) { + SelectOption(option, kDeselectOtherOptions | kMakeOptionDirty | + kDispatchInputAndChangeEvent); + } + + if (handled) + event->SetDefaultHandled(); + } + + if (event->type() == EventTypeNames::keypress) { + if (!GetLayoutObject() || !event->IsKeyboardEvent()) + return; + + int key_code = ToKeyboardEvent(event)->keyCode(); + if (key_code == ' ' && + IsSpatialNavigationEnabled(GetDocument().GetFrame())) { + // Use space to toggle arrow key handling for selection change or + // spatial navigation. + active_selection_state_ = !active_selection_state_; + event->SetDefaultHandled(); + return; + } + + KeyboardEvent* key_event = ToKeyboardEvent(event); + if (ShouldOpenPopupForKeyPressEvent(key_event)) { + HandlePopupOpenKeyboardEvent(event); + return; + } + + if (!LayoutTheme::GetTheme().PopsMenuByReturnKey() && key_code == '\r') { + if (Form()) + Form()->SubmitImplicitly(event, false); + DispatchInputAndChangeEventForMenuList(); + event->SetDefaultHandled(); + } + } + + if (event->type() == EventTypeNames::mousedown && event->IsMouseEvent() && + ToMouseEvent(event)->button() == + static_cast<short>(WebPointerProperties::Button::kLeft)) { + InputDeviceCapabilities* source_capabilities = + GetDocument() + .domWindow() + ->GetInputDeviceCapabilities() + ->FiresTouchEvents(ToMouseEvent(event)->FromTouch()); + focus(FocusParams(SelectionBehaviorOnFocus::kRestore, kWebFocusTypeNone, + source_capabilities)); + if (GetLayoutObject() && GetLayoutObject()->IsMenuList() && + !IsDisabledFormControl()) { + if (PopupIsVisible()) { + HidePopup(); + } else { + // Save the selection so it can be compared to the new selection + // when we call onChange during selectOption, which gets called + // from selectOptionByPopup, which gets called after the user + // makes a selection from the menu. + SaveLastSelection(); + // TODO(lanwei): Will check if we need to add + // InputDeviceCapabilities here when select menu list gets + // focus, see https://crbug.com/476530. + ShowPopup(); + } + } + event->SetDefaultHandled(); + } +} + +void HTMLSelectElement::UpdateSelectedState(HTMLOptionElement* clicked_option, + bool multi, + bool shift) { + DCHECK(clicked_option); + // Save the selection so it can be compared to the new selection when + // dispatching change events during mouseup, or after autoscroll finishes. + SaveLastSelection(); + + active_selection_state_ = true; + + bool shift_select = is_multiple_ && shift; + bool multi_select = is_multiple_ && multi && !shift; + + // Keep track of whether an active selection (like during drag selection), + // should select or deselect. + if (clicked_option->Selected() && multi_select) { + active_selection_state_ = false; + clicked_option->SetSelectedState(false); + clicked_option->SetDirty(true); + } + + // If we're not in any special multiple selection mode, then deselect all + // other items, excluding the clicked option. If no option was clicked, then + // this will deselect all items in the list. + if (!shift_select && !multi_select) + DeselectItemsWithoutValidation(clicked_option); + + // If the anchor hasn't been set, and we're doing a single selection or a + // shift selection, then initialize the anchor to the first selected index. + if (!active_selection_anchor_ && !multi_select) + SetActiveSelectionAnchor(SelectedOption()); + + // Set the selection state of the clicked option. + if (!clicked_option->IsDisabledFormControl()) { + clicked_option->SetSelectedState(true); + clicked_option->SetDirty(true); + } + + // If there was no selectedIndex() for the previous initialization, or If + // we're doing a single selection, or a multiple selection (using cmd or + // ctrl), then initialize the anchor index to the listIndex that just got + // clicked. + if (!active_selection_anchor_ || !shift_select) + SetActiveSelectionAnchor(clicked_option); + + SetActiveSelectionEnd(clicked_option); + UpdateListBoxSelection(!multi_select); +} + +HTMLOptionElement* HTMLSelectElement::EventTargetOption(const Event& event) { + Node* target_node = event.target()->ToNode(); + if (!target_node || !IsHTMLOptionElement(*target_node)) + return nullptr; + return ToHTMLOptionElement(target_node); +} + +int HTMLSelectElement::ListIndexForOption(const HTMLOptionElement& option) { + const ListItems& items = GetListItems(); + size_t length = items.size(); + for (size_t i = 0; i < length; ++i) { + if (items[i].Get() == &option) + return i; + } + return -1; +} + +AutoscrollController* HTMLSelectElement::GetAutoscrollController() const { + if (Page* page = GetDocument().GetPage()) + return &page->GetAutoscrollController(); + return nullptr; +} + +void HTMLSelectElement::HandleMouseRelease() { + // We didn't start this click/drag on any options. + if (last_on_change_selection_.IsEmpty()) + return; + ListBoxOnChange(); +} + +void HTMLSelectElement::ListBoxDefaultEventHandler(Event* event) { + if (event->type() == EventTypeNames::gesturetap && event->IsGestureEvent()) { + focus(); + // Calling focus() may cause us to lose our layoutObject or change the + // layoutObject type, in which case do not want to handle the event. + if (!GetLayoutObject() || !GetLayoutObject()->IsListBox()) + return; + + // Convert to coords relative to the list box if needed. + GestureEvent& gesture_event = ToGestureEvent(*event); + if (HTMLOptionElement* option = EventTargetOption(gesture_event)) { + if (!IsDisabledFormControl()) { + UpdateSelectedState(option, true, gesture_event.shiftKey()); + ListBoxOnChange(); + } + event->SetDefaultHandled(); + } + + } else if (event->type() == EventTypeNames::mousedown && + event->IsMouseEvent() && + ToMouseEvent(event)->button() == + static_cast<short>(WebPointerProperties::Button::kLeft)) { + focus(); + // Calling focus() may cause us to lose our layoutObject, in which case + // do not want to handle the event. + if (!GetLayoutObject() || !GetLayoutObject()->IsListBox() || + IsDisabledFormControl()) + return; + + // Convert to coords relative to the list box if needed. + MouseEvent* mouse_event = ToMouseEvent(event); + if (HTMLOptionElement* option = EventTargetOption(*mouse_event)) { + if (!option->IsDisabledFormControl()) { +#if defined(OS_MACOSX) + UpdateSelectedState(option, mouse_event->metaKey(), + mouse_event->shiftKey()); +#else + UpdateSelectedState(option, mouse_event->ctrlKey(), + mouse_event->shiftKey()); +#endif + } + if (LocalFrame* frame = GetDocument().GetFrame()) + frame->GetEventHandler().SetMouseDownMayStartAutoscroll(); + + event->SetDefaultHandled(); + } + + } else if (event->type() == EventTypeNames::mousemove && + event->IsMouseEvent()) { + MouseEvent* mouse_event = ToMouseEvent(event); + if (mouse_event->button() != + static_cast<short>(WebPointerProperties::Button::kLeft) || + !mouse_event->ButtonDown()) + return; + + if (LayoutObject* object = GetLayoutObject()) + object->GetFrameView()->UpdateAllLifecyclePhasesExceptPaint(); + + if (Page* page = GetDocument().GetPage()) { + page->GetAutoscrollController().StartAutoscrollForSelection( + GetLayoutObject()); + } + // Mousedown didn't happen in this element. + if (last_on_change_selection_.IsEmpty()) + return; + + if (HTMLOptionElement* option = EventTargetOption(*mouse_event)) { + if (!IsDisabledFormControl()) { + if (is_multiple_) { + // Only extend selection if there is something selected. + if (!active_selection_anchor_) + return; + + SetActiveSelectionEnd(option); + UpdateListBoxSelection(false); + } else { + SetActiveSelectionAnchor(option); + SetActiveSelectionEnd(option); + UpdateListBoxSelection(true); + } + } + } + + } else if (event->type() == EventTypeNames::mouseup && + event->IsMouseEvent() && + ToMouseEvent(event)->button() == + static_cast<short>(WebPointerProperties::Button::kLeft) && + GetLayoutObject()) { + if (GetDocument().GetPage() && + GetDocument() + .GetPage() + ->GetAutoscrollController() + .AutoscrollInProgressFor(ToLayoutBox(GetLayoutObject()))) + GetDocument().GetPage()->GetAutoscrollController().StopAutoscroll(); + else + HandleMouseRelease(); + + } else if (event->type() == EventTypeNames::keydown) { + if (!event->IsKeyboardEvent()) + return; + const String& key = ToKeyboardEvent(event)->key(); + + bool handled = false; + HTMLOptionElement* end_option = nullptr; + if (!active_selection_end_) { + // Initialize the end index + if (key == "ArrowDown" || key == "PageDown") { + HTMLOptionElement* start_option = LastSelectedOption(); + handled = true; + if (key == "ArrowDown") { + end_option = NextSelectableOption(start_option); + } else { + end_option = + NextSelectableOptionPageAway(start_option, kSkipForwards); + } + } else if (key == "ArrowUp" || key == "PageUp") { + HTMLOptionElement* start_option = SelectedOption(); + handled = true; + if (key == "ArrowUp") { + end_option = PreviousSelectableOption(start_option); + } else { + end_option = + NextSelectableOptionPageAway(start_option, kSkipBackwards); + } + } + } else { + // Set the end index based on the current end index. + if (key == "ArrowDown") { + end_option = NextSelectableOption(active_selection_end_.Get()); + handled = true; + } else if (key == "ArrowUp") { + end_option = PreviousSelectableOption(active_selection_end_.Get()); + handled = true; + } else if (key == "PageDown") { + end_option = NextSelectableOptionPageAway(active_selection_end_.Get(), + kSkipForwards); + handled = true; + } else if (key == "PageUp") { + end_option = NextSelectableOptionPageAway(active_selection_end_.Get(), + kSkipBackwards); + handled = true; + } + } + if (key == "Home") { + end_option = FirstSelectableOption(); + handled = true; + } else if (key == "End") { + end_option = LastSelectableOption(); + handled = true; + } + + if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) { + // Check if the selection moves to the boundary. + if (key == "ArrowLeft" || key == "ArrowRight" || + ((key == "ArrowDown" || key == "ArrowUp") && + end_option == active_selection_end_)) + return; + } + + if (end_option && handled) { + // Save the selection so it can be compared to the new selection + // when dispatching change events immediately after making the new + // selection. + SaveLastSelection(); + + SetActiveSelectionEnd(end_option); + + bool select_new_item = + !is_multiple_ || ToKeyboardEvent(event)->shiftKey() || + !IsSpatialNavigationEnabled(GetDocument().GetFrame()); + if (select_new_item) + active_selection_state_ = true; + // If the anchor is unitialized, or if we're going to deselect all + // other options, then set the anchor index equal to the end index. + bool deselect_others = + !is_multiple_ || + (!ToKeyboardEvent(event)->shiftKey() && select_new_item); + if (!active_selection_anchor_ || deselect_others) { + if (deselect_others) + DeselectItemsWithoutValidation(); + SetActiveSelectionAnchor(active_selection_end_.Get()); + } + + ScrollToOption(end_option); + if (select_new_item) { + UpdateListBoxSelection(deselect_others); + ListBoxOnChange(); + } else { + ScrollToSelection(); + } + + event->SetDefaultHandled(); + } + + } else if (event->type() == EventTypeNames::keypress) { + if (!event->IsKeyboardEvent()) + return; + int key_code = ToKeyboardEvent(event)->keyCode(); + + if (key_code == '\r') { + if (Form()) + Form()->SubmitImplicitly(event, false); + event->SetDefaultHandled(); + } else if (is_multiple_ && key_code == ' ' && + IsSpatialNavigationEnabled(GetDocument().GetFrame())) { + // Use space to toggle selection change. + active_selection_state_ = !active_selection_state_; + UpdateSelectedState(active_selection_end_.Get(), true /*multi*/, + false /*shift*/); + ListBoxOnChange(); + event->SetDefaultHandled(); + } + } +} + +void HTMLSelectElement::DefaultEventHandler(Event* event) { + if (!GetLayoutObject()) + return; + + if (IsDisabledFormControl()) { + HTMLFormControlElementWithState::DefaultEventHandler(event); + return; + } + + if (UsesMenuList()) + MenuListDefaultEventHandler(event); + else + ListBoxDefaultEventHandler(event); + if (event->DefaultHandled()) + return; + + if (event->type() == EventTypeNames::keypress && event->IsKeyboardEvent()) { + KeyboardEvent* keyboard_event = ToKeyboardEvent(event); + if (!keyboard_event->ctrlKey() && !keyboard_event->altKey() && + !keyboard_event->metaKey() && + WTF::Unicode::IsPrintableChar(keyboard_event->charCode())) { + TypeAheadFind(keyboard_event); + event->SetDefaultHandled(); + return; + } + } + HTMLFormControlElementWithState::DefaultEventHandler(event); +} + +HTMLOptionElement* HTMLSelectElement::LastSelectedOption() const { + const ListItems& items = GetListItems(); + for (size_t i = items.size(); i;) { + if (HTMLOptionElement* option = OptionAtListIndex(--i)) { + if (option->Selected()) + return option; + } + } + return nullptr; +} + +int HTMLSelectElement::IndexOfSelectedOption() const { + return SelectedListIndex(); +} + +int HTMLSelectElement::OptionCount() const { + return GetListItems().size(); +} + +String HTMLSelectElement::OptionAtIndex(int index) const { + if (HTMLOptionElement* option = OptionAtListIndex(index)) { + if (!option->IsDisabledFormControl()) + return option->DisplayLabel(); + } + return String(); +} + +void HTMLSelectElement::TypeAheadFind(KeyboardEvent* event) { + int index = type_ahead_.HandleEvent( + event, TypeAhead::kMatchPrefix | TypeAhead::kCycleFirstChar); + if (index < 0) + return; + SelectOption(OptionAtListIndex(index), kDeselectOtherOptions | + kMakeOptionDirty | + kDispatchInputAndChangeEvent); + if (!UsesMenuList()) + ListBoxOnChange(); +} + +void HTMLSelectElement::SelectOptionByAccessKey(HTMLOptionElement* option) { + // First bring into focus the list box. + if (!IsFocused()) + AccessKeyAction(false); + + if (!option || option->OwnerSelectElement() != this) + return; + EventQueueScope scope; + // If this index is already selected, unselect. otherwise update the + // selected index. + SelectOptionFlags flags = + kDispatchInputAndChangeEvent | (IsMultiple() ? 0 : kDeselectOtherOptions); + if (option->Selected()) { + if (UsesMenuList()) + SelectOption(nullptr, flags); + else + option->SetSelectedState(false); + } else { + SelectOption(option, flags); + } + option->SetDirty(true); + if (UsesMenuList()) + return; + ListBoxOnChange(); + ScrollToSelection(); +} + +unsigned HTMLSelectElement::length() const { + unsigned options = 0; + for (auto* const option : GetOptionList()) { + ALLOW_UNUSED_LOCAL(option); + ++options; + } + return options; +} + +void HTMLSelectElement::FinishParsingChildren() { + HTMLFormControlElementWithState::FinishParsingChildren(); + if (UsesMenuList()) + return; + ScrollToOption(SelectedOption()); + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) + cache->ListboxActiveIndexChanged(this); +} + +bool HTMLSelectElement::AnonymousIndexedSetter( + unsigned index, + HTMLOptionElement* value, + ExceptionState& exception_state) { + if (!value) { // undefined or null + remove(index); + return true; + } + SetOption(index, value, exception_state); + return true; +} + +bool HTMLSelectElement::IsInteractiveContent() const { + return true; +} + +bool HTMLSelectElement::SupportsAutofocus() const { + return true; +} + +void HTMLSelectElement::UpdateListOnLayoutObject() { + SetOptionsChangedOnLayoutObject(); +} + +void HTMLSelectElement::Trace(blink::Visitor* visitor) { + visitor->Trace(list_items_); + visitor->Trace(last_on_change_option_); + visitor->Trace(active_selection_anchor_); + visitor->Trace(active_selection_end_); + visitor->Trace(option_to_scroll_to_); + visitor->Trace(suggested_option_); + visitor->Trace(popup_); + visitor->Trace(popup_updater_); + HTMLFormControlElementWithState::Trace(visitor); +} + +void HTMLSelectElement::DidAddUserAgentShadowRoot(ShadowRoot& root) { + root.AppendChild( + HTMLSlotElement::CreateUserAgentCustomAssignSlot(GetDocument())); +} + +HTMLOptionElement* HTMLSelectElement::SpatialNavigationFocusedOption() { + if (!IsSpatialNavigationEnabled(GetDocument().GetFrame())) + return nullptr; + HTMLOptionElement* focused_option = ActiveSelectionEnd(); + if (!focused_option) + focused_option = FirstSelectableOption(); + return focused_option; +} + +String HTMLSelectElement::ItemText(const Element& element) const { + String item_string; + if (auto* optgroup = ToHTMLOptGroupElementOrNull(element)) + item_string = optgroup->GroupLabelText(); + else if (auto* option = ToHTMLOptionElementOrNull(element)) + item_string = option->TextIndentedToRespectGroupLabel(); + + if (GetLayoutObject()) + ApplyTextTransform(GetLayoutObject()->Style(), item_string, ' '); + return item_string; +} + +bool HTMLSelectElement::ItemIsDisplayNone(Element& element) const { + if (auto* option = ToHTMLOptionElementOrNull(element)) + return option->IsDisplayNone(); + const ComputedStyle* style = ItemComputedStyle(element); + return !style || style->Display() == EDisplay::kNone; +} + +const ComputedStyle* HTMLSelectElement::ItemComputedStyle( + Element& element) const { + return element.GetComputedStyle() ? element.GetComputedStyle() + : element.EnsureComputedStyle(); +} + +LayoutUnit HTMLSelectElement::ClientPaddingLeft() const { + if (GetLayoutObject() && GetLayoutObject()->IsMenuList()) + return ToLayoutMenuList(GetLayoutObject())->ClientPaddingLeft(); + return LayoutUnit(); +} + +LayoutUnit HTMLSelectElement::ClientPaddingRight() const { + if (GetLayoutObject() && GetLayoutObject()->IsMenuList()) + return ToLayoutMenuList(GetLayoutObject())->ClientPaddingRight(); + return LayoutUnit(); +} + +void HTMLSelectElement::PopupDidHide() { + popup_is_visible_ = false; + UnobserveTreeMutation(); + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { + if (GetLayoutObject() && GetLayoutObject()->IsMenuList()) + cache->DidHideMenuListPopup(ToLayoutMenuList(GetLayoutObject())); + } +} + +void HTMLSelectElement::SetIndexToSelectOnCancel(int list_index) { + index_to_select_on_cancel_ = list_index; + if (GetLayoutObject()) + GetLayoutObject()->UpdateFromElement(); +} + +HTMLOptionElement* HTMLSelectElement::OptionToBeShown() const { + if (HTMLOptionElement* option = OptionAtListIndex(index_to_select_on_cancel_)) + return option; + if (suggested_option_) + return suggested_option_; + // TODO(tkent): We should not call optionToBeShown() in isMultiple() case. + if (IsMultiple()) + return SelectedOption(); + DCHECK_EQ(SelectedOption(), last_on_change_option_); + return last_on_change_option_; +} + +void HTMLSelectElement::SelectOptionByPopup(int list_index) { + DCHECK(UsesMenuList()); + // Check to ensure a page navigation has not occurred while the popup was + // up. + Document& doc = GetDocument(); + if (&doc != doc.GetFrame()->GetDocument()) + return; + + SetIndexToSelectOnCancel(-1); + + HTMLOptionElement* option = OptionAtListIndex(list_index); + // Bail out if this index is already the selected one, to avoid running + // unnecessary JavaScript that can mess up autofill when there is no actual + // change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and + // <rdar://7467917>). The selectOption function does not behave this way, + // possibly because other callers need a change event even in cases where + // the selected option is not change. + if (option == SelectedOption()) + return; + SelectOption(option, kDeselectOtherOptions | kMakeOptionDirty | + kDispatchInputAndChangeEvent); +} + +void HTMLSelectElement::PopupDidCancel() { + if (index_to_select_on_cancel_ >= 0) + SelectOptionByPopup(index_to_select_on_cancel_); +} + +void HTMLSelectElement::ProvisionalSelectionChanged(unsigned list_index) { + SetIndexToSelectOnCancel(list_index); +} + +void HTMLSelectElement::ShowPopup() { + if (PopupIsVisible()) + return; + if (GetDocument().GetPage()->GetChromeClient().HasOpenedPopup()) + return; + if (!GetLayoutObject() || !GetLayoutObject()->IsMenuList()) + return; + if (VisibleBoundsInVisualViewport().IsEmpty()) + return; + + if (!popup_) { + popup_ = GetDocument().GetPage()->GetChromeClient().OpenPopupMenu( + *GetDocument().GetFrame(), *this); + } + if (!popup_) + return; + + popup_is_visible_ = true; + ObserveTreeMutation(); + + LayoutMenuList* menu_list = ToLayoutMenuList(GetLayoutObject()); + popup_->Show(); + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) + cache->DidShowMenuListPopup(menu_list); +} + +void HTMLSelectElement::HidePopup() { + if (popup_) + popup_->Hide(); +} + +void HTMLSelectElement::DidRecalcStyle(StyleRecalcChange change) { + HTMLFormControlElementWithState::DidRecalcStyle(change); + if (PopupIsVisible()) + popup_->UpdateFromElement(PopupMenu::kByStyleChange); +} + +void HTMLSelectElement::DetachLayoutTree(const AttachContext& context) { + HTMLFormControlElementWithState::DetachLayoutTree(context); + if (popup_) + popup_->DisconnectClient(); + popup_is_visible_ = false; + popup_ = nullptr; + UnobserveTreeMutation(); +} + +void HTMLSelectElement::ResetTypeAheadSessionForTesting() { + type_ahead_.ResetSession(); +} + +// PopupUpdater notifies updates of the specified SELECT element subtree to +// a PopupMenu object. +class HTMLSelectElement::PopupUpdater : public MutationObserver::Delegate { + public: + explicit PopupUpdater(HTMLSelectElement& select) + : select_(select), observer_(MutationObserver::Create(this)) { + MutationObserverInit init; + init.setAttributeOldValue(true); + init.setAttributes(true); + // Observe only attributes which affect popup content. + init.setAttributeFilter({"disabled", "label", "selected", "value"}); + init.setCharacterData(true); + init.setCharacterDataOldValue(true); + init.setChildList(true); + init.setSubtree(true); + observer_->observe(select_, init, ASSERT_NO_EXCEPTION); + } + + ExecutionContext* GetExecutionContext() const override { + return &select_->GetDocument(); + } + + void Deliver(const MutationRecordVector& records, + MutationObserver&) override { + // We disconnect the MutationObserver when a popup is closed. However + // MutationObserver can call back after disconnection. + if (!select_->PopupIsVisible()) + return; + for (const auto& record : records) { + if (record->type() == "attributes") { + const Element& element = *ToElement(record->target()); + if (record->oldValue() == element.getAttribute(record->attributeName())) + continue; + } else if (record->type() == "characterData") { + if (record->oldValue() == record->target()->nodeValue()) + continue; + } + select_->DidMutateSubtree(); + return; + } + } + + void Dispose() { observer_->disconnect(); } + + void Trace(blink::Visitor* visitor) override { + visitor->Trace(select_); + visitor->Trace(observer_); + MutationObserver::Delegate::Trace(visitor); + } + + private: + Member<HTMLSelectElement> select_; + Member<MutationObserver> observer_; +}; + +void HTMLSelectElement::ObserveTreeMutation() { + DCHECK(!popup_updater_); + popup_updater_ = new PopupUpdater(*this); +} + +void HTMLSelectElement::UnobserveTreeMutation() { + if (!popup_updater_) + return; + popup_updater_->Dispose(); + popup_updater_ = nullptr; +} + +void HTMLSelectElement::DidMutateSubtree() { + DCHECK(PopupIsVisible()); + DCHECK(popup_); + popup_->UpdateFromElement(PopupMenu::kByDOMChange); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h new file mode 100644 index 00000000000..76a8880e2f3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.h @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights + * reserved. + * Copyright (C) 2010 Google Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_ + +#include "base/gtest_prod_util.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" +#include "third_party/blink/renderer/core/html/forms/html_options_collection.h" +#include "third_party/blink/renderer/core/html/forms/option_list.h" +#include "third_party/blink/renderer/core/html/forms/type_ahead.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class AutoscrollController; +class ExceptionState; +class HTMLHRElement; +class HTMLOptGroupElement; +class HTMLOptionElement; +class HTMLOptionElementOrHTMLOptGroupElement; +class HTMLElementOrLong; +class PopupMenu; + +class CORE_EXPORT HTMLSelectElement final + : public HTMLFormControlElementWithState, + private TypeAheadDataSource { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLSelectElement* Create(Document&); + ~HTMLSelectElement() override; + + int selectedIndex() const; + void setSelectedIndex(int); + // `listIndex' version of |selectedIndex|. + int SelectedListIndex() const; + + // For ValidityState + String validationMessage() const override; + bool ValueMissing() const override; + + String DefaultToolTip() const override; + void ResetImpl() override; + + unsigned length() const; + void setLength(unsigned, ExceptionState&); + + // TODO(tkent): Rename |size| to |Size|. This is not an implementation of + // |size| IDL attribute. + unsigned size() const { return size_; } + bool IsMultiple() const { return is_multiple_; } + + bool UsesMenuList() const; + + void add(const HTMLOptionElementOrHTMLOptGroupElement&, + const HTMLElementOrLong&, + ExceptionState&); + + using Node::remove; + void remove(int index); + + String value() const; + void setValue(const String&, bool send_events = false); + String SuggestedValue() const; + void SetSuggestedValue(const String&); + + // |options| and |selectedOptions| are not safe to be used in in + // HTMLOptionElement::removedFrom() and insertedInto() because their cache + // is inconsistent in these functions. + HTMLOptionsCollection* options(); + HTMLCollection* selectedOptions(); + + // This is similar to |options| HTMLCollection. But this is safe in + // HTMLOptionElement::removedFrom() and insertedInto(). + // OptionList supports only forward iteration. + OptionList GetOptionList() const { return OptionList(*this); } + + void OptionElementChildrenChanged(const HTMLOptionElement&); + + void InvalidateSelectedItems(); + + using ListItems = HeapVector<Member<HTMLElement>>; + // We prefer |optionList()| to |listItems()|. + const ListItems& GetListItems() const; + + void AccessKeyAction(bool send_mouse_events) override; + void SelectOptionByAccessKey(HTMLOptionElement*); + + void SetOption(unsigned index, HTMLOptionElement*, ExceptionState&); + + Element* namedItem(const AtomicString& name); + HTMLOptionElement* item(unsigned index); + + void ScrollToSelection(); + void ScrollToOption(HTMLOptionElement*); + + bool CanSelectAll() const; + void SelectAll(); + void ListBoxOnChange(); + int ActiveSelectionEndListIndex() const; + HTMLOptionElement* ActiveSelectionEnd() const; + void SetActiveSelectionAnchor(HTMLOptionElement*); + void SetActiveSelectionEnd(HTMLOptionElement*); + + // For use in the implementation of HTMLOptionElement. + void OptionSelectionStateChanged(HTMLOptionElement*, bool option_is_selected); + void OptionInserted(HTMLOptionElement&, bool option_is_selected); + void OptionRemoved(HTMLOptionElement&); + bool AnonymousIndexedSetter(unsigned, HTMLOptionElement*, ExceptionState&); + + void OptGroupInsertedOrRemoved(HTMLOptGroupElement&); + void HrInsertedOrRemoved(HTMLHRElement&); + + void UpdateListOnLayoutObject(); + + HTMLOptionElement* SpatialNavigationFocusedOption(); + void HandleMouseRelease(); + + int ListIndexForOption(const HTMLOptionElement&); + + // Helper functions for popup menu implementations. + String ItemText(const Element&) const; + bool ItemIsDisplayNone(Element&) const; + // itemComputedStyle() returns nullptr only if the owner Document is not + // active. So, It returns a valid object when we open a popup. + const ComputedStyle* ItemComputedStyle(Element&) const; + // Text starting offset in LTR. + LayoutUnit ClientPaddingLeft() const; + // Text starting offset in RTL. + LayoutUnit ClientPaddingRight() const; + void SelectOptionByPopup(int list_index); + void SelectMultipleOptionsByPopup(const Vector<int>& list_indices); + // A popup is canceled when the popup was hidden without selecting an item. + void PopupDidCancel(); + // Provisional selection is a selection made using arrow keys or type ahead. + void ProvisionalSelectionChanged(unsigned); + void PopupDidHide(); + bool PopupIsVisible() const { return popup_is_visible_; } + HTMLOptionElement* OptionToBeShown() const; + void ShowPopup(); + void HidePopup(); + PopupMenu* Popup() const { return popup_.Get(); } + void DidMutateSubtree(); + + void ResetTypeAheadSessionForTesting(); + + // Used for slot assignment. + static bool CanAssignToSelectSlot(const Node&); + + bool HasNonInBodyInsertionMode() const override { return true; } + + void Trace(blink::Visitor*) override; + + protected: + explicit HTMLSelectElement(Document&); + + private: + const AtomicString& FormControlType() const override; + + bool ShouldShowFocusRingOnMouseFocus() const override; + + void DispatchFocusEvent( + Element* old_focused_element, + WebFocusType, + InputDeviceCapabilities* source_capabilities) override; + void DispatchBlurEvent(Element* new_focused_element, + WebFocusType, + InputDeviceCapabilities* source_capabilities) override; + + bool CanStartSelection() const override { return false; } + + bool IsEnumeratable() const override { return true; } + bool IsInteractiveContent() const override; + bool SupportsAutofocus() const override; + bool SupportLabels() const override { return true; } + + FormControlState SaveFormControlState() const override; + void RestoreFormControlState(const FormControlState&) override; + + void ParseAttribute(const AttributeModificationParams&) override; + bool IsPresentationAttribute(const QualifiedName&) const override; + + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + void DidRecalcStyle(StyleRecalcChange) override; + void DetachLayoutTree(const AttachContext& = AttachContext()) override; + void AppendToFormData(FormData&) override; + void DidAddUserAgentShadowRoot(ShadowRoot&) override; + + void DefaultEventHandler(Event*) override; + + void DispatchInputAndChangeEventForMenuList(); + + void SetRecalcListItems(); + void RecalcListItems() const; + enum ResetReason { kResetReasonSelectedOptionRemoved, kResetReasonOthers }; + void ResetToDefaultSelection(ResetReason = kResetReasonOthers); + void TypeAheadFind(KeyboardEvent*); + void SaveLastSelection(); + void SaveListboxActiveSelection(); + // Returns the first selected OPTION, or nullptr. + HTMLOptionElement* SelectedOption() const; + + bool IsOptionalFormControl() const override { + return !IsRequiredFormControl(); + } + bool IsRequiredFormControl() const override; + + bool HasPlaceholderLabelOption() const; + + enum SelectOptionFlag { + kDeselectOtherOptions = 1 << 0, + kDispatchInputAndChangeEvent = 1 << 1, + kMakeOptionDirty = 1 << 2, + }; + typedef unsigned SelectOptionFlags; + void SelectOption(HTMLOptionElement*, SelectOptionFlags); + bool DeselectItemsWithoutValidation( + HTMLOptionElement* element_to_exclude = nullptr); + void ParseMultipleAttribute(const AtomicString&); + HTMLOptionElement* LastSelectedOption() const; + void UpdateSelectedState(HTMLOptionElement*, bool multi, bool shift); + void MenuListDefaultEventHandler(Event*); + void HandlePopupOpenKeyboardEvent(Event*); + bool ShouldOpenPopupForKeyDownEvent(KeyboardEvent*); + bool ShouldOpenPopupForKeyPressEvent(KeyboardEvent*); + void ListBoxDefaultEventHandler(Event*); + void SetOptionsChangedOnLayoutObject(); + size_t SearchOptionsForValue(const String&, + size_t list_index_start, + size_t list_index_end) const; + void UpdateListBoxSelection(bool deselect_other_options, bool scroll = true); + void SetIndexToSelectOnCancel(int list_index); + void SetSuggestedOption(HTMLOptionElement*); + + // Returns nullptr if listIndex is out of bounds, or it doesn't point an + // HTMLOptionElement. + HTMLOptionElement* OptionAtListIndex(int list_index) const; + enum SkipDirection { kSkipBackwards = -1, kSkipForwards = 1 }; + HTMLOptionElement* NextValidOption(int list_index, + SkipDirection, + int skip) const; + HTMLOptionElement* NextSelectableOption(HTMLOptionElement*) const; + HTMLOptionElement* PreviousSelectableOption(HTMLOptionElement*) const; + HTMLOptionElement* FirstSelectableOption() const; + HTMLOptionElement* LastSelectableOption() const; + HTMLOptionElement* NextSelectableOptionPageAway(HTMLOptionElement*, + SkipDirection) const; + HTMLOptionElement* EventTargetOption(const Event&); + AutoscrollController* GetAutoscrollController() const; + void ScrollToOptionTask(); + + bool AreAuthorShadowsAllowed() const override { return false; } + void FinishParsingChildren() override; + + // TypeAheadDataSource functions. + int IndexOfSelectedOption() const override; + int OptionCount() const override; + String OptionAtIndex(int index) const override; + + void ObserveTreeMutation(); + void UnobserveTreeMutation(); + + // m_listItems contains HTMLOptionElement, HTMLOptGroupElement, and + // HTMLHRElement objects. + mutable ListItems list_items_; + Vector<bool> last_on_change_selection_; + Vector<bool> cached_state_for_active_selection_; + TypeAhead type_ahead_; + unsigned size_; + Member<HTMLOptionElement> last_on_change_option_; + Member<HTMLOptionElement> active_selection_anchor_; + Member<HTMLOptionElement> active_selection_end_; + Member<HTMLOptionElement> option_to_scroll_to_; + Member<HTMLOptionElement> suggested_option_; + bool is_multiple_; + bool active_selection_state_; + mutable bool should_recalc_list_items_; + bool is_autofilled_by_preview_; + + class PopupUpdater; + Member<PopupUpdater> popup_updater_; + Member<PopupMenu> popup_; + int index_to_select_on_cancel_; + bool popup_is_visible_; + + FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, FirstSelectableOption); + FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, LastSelectableOption); + FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, NextSelectableOption); + FRIEND_TEST_ALL_PREFIXES(HTMLSelectElementTest, PreviousSelectableOption); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_SELECT_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl new file mode 100644 index 00000000000..05e4a3703fd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.idl @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2013 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. + */ + +// https://html.spec.whatwg.org/#the-select-element +[HTMLConstructor] +interface HTMLSelectElement : HTMLElement { + [CEReactions, Reflect] attribute DOMString autocomplete; + [CEReactions, Reflect] attribute boolean autofocus; + [CEReactions, Reflect] attribute boolean disabled; + [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form; + [CEReactions, Reflect] attribute boolean multiple; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute boolean required; + [CEReactions, Reflect] attribute unsigned long size; + + readonly attribute DOMString type; + + readonly attribute HTMLOptionsCollection options; + // TODO(foolip): The length setter should never throw. + [CEReactions, RaisesException=Setter] attribute unsigned long length; + getter Element? item(unsigned long index); + HTMLOptionElement? namedItem(DOMString name); + [CEReactions, RaisesException] void add((HTMLOptionElement or HTMLOptGroupElement) element, + optional (HTMLElement or long)? before = null); + [CEReactions, RaisesException] void remove(); // ChildNode overload + [CEReactions] void remove(long index); + [CEReactions, RaisesException] setter void (unsigned long index, HTMLOptionElement? option); + + readonly attribute HTMLCollection selectedOptions; + attribute long selectedIndex; + attribute DOMString value; + + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); + + readonly attribute NodeList labels; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc new file mode 100644 index 00000000000..0b21a2d6330 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element_test.cc @@ -0,0 +1,449 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/testing/page_test_base.h" + +namespace blink { + +class HTMLSelectElementTest : public PageTestBase { + protected: + void SetUp() override; +}; + +void HTMLSelectElementTest::SetUp() { + Page::PageClients page_clients; + FillWithEmptyClients(page_clients); + PageTestBase::SetupPageWithClients(&page_clients); + GetDocument().SetMimeType("text/html"); +} + +TEST_F(HTMLSelectElementTest, SaveRestoreSelectSingleFormControlState) { + SetHtmlInnerHTML( + "<!DOCTYPE HTML><select id='sel'>" + "<option value='111' id='0'>111</option>" + "<option value='222'>222</option>" + "<option value='111' selected id='2'>!666</option>" + "<option value='999'>999</option></select>"); + Element* element = GetElementById("sel"); + HTMLFormControlElementWithState* select = ToHTMLSelectElement(element); + HTMLOptionElement* opt0 = ToHTMLOptionElement(GetElementById("0")); + HTMLOptionElement* opt2 = ToHTMLOptionElement(GetElementById("2")); + + // Save the select element state, and then restore again. + // Test passes if the restored state is not changed. + EXPECT_EQ(2, ToHTMLSelectElement(element)->selectedIndex()); + EXPECT_FALSE(opt0->Selected()); + EXPECT_TRUE(opt2->Selected()); + FormControlState select_state = select->SaveFormControlState(); + EXPECT_EQ(2U, select_state.ValueSize()); + + // Clear the selected state, to be restored by restoreFormControlState. + ToHTMLSelectElement(select)->setSelectedIndex(-1); + ASSERT_FALSE(opt2->Selected()); + + // Restore + select->RestoreFormControlState(select_state); + EXPECT_EQ(2, ToHTMLSelectElement(element)->selectedIndex()); + EXPECT_FALSE(opt0->Selected()); + EXPECT_TRUE(opt2->Selected()); +} + +TEST_F(HTMLSelectElementTest, SaveRestoreSelectMultipleFormControlState) { + SetHtmlInnerHTML( + "<!DOCTYPE HTML><select id='sel' multiple>" + "<option value='111' id='0'>111</option>" + "<option value='222'>222</option>" + "<option value='111' selected id='2'>!666</option>" + "<option value='999' selected id='3'>999</option></select>"); + HTMLFormControlElementWithState* select = + ToHTMLSelectElement(GetElementById("sel")); + + HTMLOptionElement* opt0 = ToHTMLOptionElement(GetElementById("0")); + HTMLOptionElement* opt2 = ToHTMLOptionElement(GetElementById("2")); + HTMLOptionElement* opt3 = ToHTMLOptionElement(GetElementById("3")); + + // Save the select element state, and then restore again. + // Test passes if the selected options are not changed. + EXPECT_FALSE(opt0->Selected()); + EXPECT_TRUE(opt2->Selected()); + EXPECT_TRUE(opt3->Selected()); + FormControlState select_state = select->SaveFormControlState(); + EXPECT_EQ(4U, select_state.ValueSize()); + + // Clear the selected state, to be restored by restoreFormControlState. + opt2->SetSelected(false); + opt3->SetSelected(false); + ASSERT_FALSE(opt2->Selected()); + ASSERT_FALSE(opt3->Selected()); + + // Restore + select->RestoreFormControlState(select_state); + EXPECT_FALSE(opt0->Selected()); + EXPECT_TRUE(opt2->Selected()); + EXPECT_TRUE(opt3->Selected()); +} + +TEST_F(HTMLSelectElementTest, RestoreUnmatchedFormControlState) { + // We had a bug that selectedOption() and m_lastOnChangeOption were + // mismatched in optionToBeShown(). It happened when + // restoreFormControlState() couldn't find matched OPTIONs. + // crbug.com/627833. + + SetHtmlInnerHTML(R"HTML( + <select id='sel'> + <option selected>Default</option> + <option id='2'>222</option> + </select> + )HTML"); + Element* element = GetElementById("sel"); + HTMLFormControlElementWithState* select = ToHTMLSelectElement(element); + HTMLOptionElement* opt2 = ToHTMLOptionElement(GetElementById("2")); + + ToHTMLSelectElement(element)->setSelectedIndex(1); + // Save the current state. + FormControlState select_state = select->SaveFormControlState(); + EXPECT_EQ(2U, select_state.ValueSize()); + + // Reset the status. + select->Reset(); + ASSERT_FALSE(opt2->Selected()); + element->RemoveChild(opt2); + + // Restore + select->RestoreFormControlState(select_state); + EXPECT_EQ(-1, ToHTMLSelectElement(element)->selectedIndex()); + EXPECT_EQ(nullptr, ToHTMLSelectElement(element)->OptionToBeShown()); +} + +TEST_F(HTMLSelectElementTest, VisibleBoundsInVisualViewport) { + SetHtmlInnerHTML( + "<select style='position:fixed; top:12.3px; height:24px; " + "-webkit-appearance:none;'><option>o1</select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + ASSERT_NE(select, nullptr); + IntRect bounds = select->VisibleBoundsInVisualViewport(); + EXPECT_EQ(24, bounds.Height()); +} + +TEST_F(HTMLSelectElementTest, PopupIsVisible) { + SetHtmlInnerHTML("<select><option>o1</option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + ASSERT_NE(select, nullptr); + EXPECT_FALSE(select->PopupIsVisible()); + select->ShowPopup(); + EXPECT_TRUE(select->PopupIsVisible()); + GetDocument().Shutdown(); + EXPECT_FALSE(select->PopupIsVisible()); +} + +TEST_F(HTMLSelectElementTest, FirstSelectableOption) { + { + SetHtmlInnerHTML("<select></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ(nullptr, select->FirstSelectableOption()); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->FirstSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1 disabled></option><option " + "id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->FirstSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1 style='display:none'></option><option " + "id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->FirstSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><optgroup><option id=o1></option><option " + "id=o2></option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->FirstSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } +} + +TEST_F(HTMLSelectElementTest, LastSelectableOption) { + { + SetHtmlInnerHTML("<select></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ(nullptr, select->LastSelectableOption()); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->LastSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2 " + "disabled></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->LastSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2 " + "style='display:none'></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->LastSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><optgroup><option id=o1></option><option " + "id=o2></option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->LastSelectableOption()->FastGetAttribute( + HTMLNames::idAttr)); + } +} + +TEST_F(HTMLSelectElementTest, NextSelectableOption) { + { + SetHtmlInnerHTML("<select></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ(nullptr, select->NextSelectableOption(nullptr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->NextSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1 disabled></option><option " + "id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->NextSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1 style='display:none'></option><option " + "id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->NextSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><optgroup><option id=o1></option><option " + "id=o2></option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->NextSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o1")); + EXPECT_EQ("o2", select->NextSelectableOption(option)->FastGetAttribute( + HTMLNames::idAttr)); + + EXPECT_EQ(nullptr, select->NextSelectableOption( + ToHTMLOptionElement(GetElementById("o2")))); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><optgroup><option " + "id=o2></option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o1")); + EXPECT_EQ("o2", select->NextSelectableOption(option)->FastGetAttribute( + HTMLNames::idAttr)); + } +} + +TEST_F(HTMLSelectElementTest, PreviousSelectableOption) { + { + SetHtmlInnerHTML("<select></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ(nullptr, select->PreviousSelectableOption(nullptr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->PreviousSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2 " + "disabled></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->PreviousSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2 " + "style='display:none'></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o1", select->PreviousSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><optgroup><option id=o1></option><option " + "id=o2></option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + EXPECT_EQ("o2", select->PreviousSelectableOption(nullptr)->FastGetAttribute( + HTMLNames::idAttr)); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><option id=o2></option></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o2")); + EXPECT_EQ("o1", select->PreviousSelectableOption(option)->FastGetAttribute( + HTMLNames::idAttr)); + + EXPECT_EQ(nullptr, select->PreviousSelectableOption( + ToHTMLOptionElement(GetElementById("o1")))); + } + { + SetHtmlInnerHTML( + "<select><option id=o1></option><optgroup><option " + "id=o2></option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + HTMLOptionElement* option = ToHTMLOptionElement(GetElementById("o2")); + EXPECT_EQ("o1", select->PreviousSelectableOption(option)->FastGetAttribute( + HTMLNames::idAttr)); + } +} + +TEST_F(HTMLSelectElementTest, ActiveSelectionEndAfterOptionRemoval) { + SetHtmlInnerHTML( + "<select><optgroup><option selected>o1</option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + HTMLOptionElement* option = + ToHTMLOptionElement(select->firstChild()->firstChild()); + EXPECT_EQ(1, select->ActiveSelectionEndListIndex()); + select->firstChild()->removeChild(option); + EXPECT_EQ(-1, select->ActiveSelectionEndListIndex()); + select->AppendChild(option); + EXPECT_EQ(1, select->ActiveSelectionEndListIndex()); +} + +TEST_F(HTMLSelectElementTest, DefaultToolTip) { + SetHtmlInnerHTML( + "<select size=4><option value=" + ">Placeholder</option><optgroup><option>o2</option></optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + Element* option = ToElement(select->firstChild()); + Element* optgroup = ToElement(option->nextSibling()); + + EXPECT_EQ(String(), select->DefaultToolTip()) + << "defaultToolTip for SELECT without FORM and without required " + "attribute should return null string."; + EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip()); + EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip()); + + select->SetBooleanAttribute(HTMLNames::requiredAttr, true); + EXPECT_EQ("<<ValidationValueMissingForSelect>>", select->DefaultToolTip()) + << "defaultToolTip for SELECT without FORM and with required attribute " + "should return a valueMissing message."; + EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip()); + EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip()); + + HTMLFormElement* form = HTMLFormElement::Create(GetDocument()); + GetDocument().body()->AppendChild(form); + form->AppendChild(select); + EXPECT_EQ("<<ValidationValueMissingForSelect>>", select->DefaultToolTip()) + << "defaultToolTip for SELECT with FORM and required attribute should " + "return a valueMissing message."; + EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip()); + EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip()); + + form->SetBooleanAttribute(HTMLNames::novalidateAttr, true); + EXPECT_EQ(String(), select->DefaultToolTip()) + << "defaultToolTip for SELECT with FORM[novalidate] and required " + "attribute should return null string."; + EXPECT_EQ(select->DefaultToolTip(), option->DefaultToolTip()); + EXPECT_EQ(select->DefaultToolTip(), optgroup->DefaultToolTip()); + + option->remove(); + optgroup->remove(); + EXPECT_EQ(String(), option->DefaultToolTip()); + EXPECT_EQ(String(), optgroup->DefaultToolTip()); +} + +TEST_F(HTMLSelectElementTest, SetRecalcListItemsByOptgroupRemoval) { + SetHtmlInnerHTML( + "<select><optgroup><option>sub1</option><option>sub2</option></" + "optgroup></select>"); + HTMLSelectElement* select = + ToHTMLSelectElement(GetDocument().body()->firstChild()); + select->SetInnerHTMLFromString(""); + // PASS if setInnerHTML didn't have a check failure. +} + +TEST_F(HTMLSelectElementTest, ScrollToOptionAfterLayoutCrash) { + // crbug.com/737447 + // This test passes if no crash. + SetHtmlInnerHTML(R"HTML( + <style>*:checked { position:fixed; }</style> + <select multiple><<option>o1</option><option + selected>o2</option></select> + )HTML"); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc new file mode 100644 index 00000000000..1e76096534f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.cc @@ -0,0 +1,636 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights + * reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) + * + * 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 "third_party/blink/renderer/core/html/forms/html_text_area_element.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/css_value_keywords.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/exception_code.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h" +#include "third_party/blink/renderer/core/events/before_text_inserted_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_text_control_multi_line.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +using namespace HTMLNames; + +static const unsigned kDefaultRows = 2; +static const unsigned kDefaultCols = 20; + +static inline unsigned ComputeLengthForAPIValue(const String& text) { + unsigned length = text.length(); + unsigned crlf_count = 0; + for (unsigned i = 0; i < length; ++i) { + if (text[i] == '\r' && i + 1 < length && text[i + 1] == '\n') + crlf_count++; + } + return text.length() - crlf_count; +} + +HTMLTextAreaElement::HTMLTextAreaElement(Document& document) + : TextControlElement(textareaTag, document), + rows_(kDefaultRows), + cols_(kDefaultCols), + wrap_(kSoftWrap), + is_dirty_(false), + is_placeholder_visible_(false) {} + +HTMLTextAreaElement* HTMLTextAreaElement::Create(Document& document) { + HTMLTextAreaElement* text_area = new HTMLTextAreaElement(document); + text_area->EnsureUserAgentShadowRoot(); + return text_area; +} + +void HTMLTextAreaElement::DidAddUserAgentShadowRoot(ShadowRoot& root) { + root.AppendChild(CreateInnerEditorElement()); +} + +const AtomicString& HTMLTextAreaElement::FormControlType() const { + DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea")); + return textarea; +} + +FormControlState HTMLTextAreaElement::SaveFormControlState() const { + return is_dirty_ ? FormControlState(value()) : FormControlState(); +} + +void HTMLTextAreaElement::RestoreFormControlState( + const FormControlState& state) { + setValue(state[0]); +} + +void HTMLTextAreaElement::ChildrenChanged(const ChildrenChange& change) { + HTMLElement::ChildrenChanged(change); + SetLastChangeWasNotUserEdit(); + if (is_dirty_) + SetInnerEditorValue(value()); + else + SetNonDirtyValue(defaultValue()); +} + +bool HTMLTextAreaElement::IsPresentationAttribute( + const QualifiedName& name) const { + if (name == alignAttr) { + // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. + // See http://bugs.webkit.org/show_bug.cgi?id=7075 + return false; + } + + if (name == wrapAttr) + return true; + return TextControlElement::IsPresentationAttribute(name); +} + +void HTMLTextAreaElement::CollectStyleForPresentationAttribute( + const QualifiedName& name, + const AtomicString& value, + MutableCSSPropertyValueSet* style) { + if (name == wrapAttr) { + if (ShouldWrapText()) { + AddPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, + CSSValuePreWrap); + AddPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, + CSSValueBreakWord); + } else { + AddPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, + CSSValuePre); + AddPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, + CSSValueNormal); + } + } else { + TextControlElement::CollectStyleForPresentationAttribute(name, value, + style); + } +} + +void HTMLTextAreaElement::ParseAttribute( + const AttributeModificationParams& params) { + const QualifiedName& name = params.name; + const AtomicString& value = params.new_value; + if (name == rowsAttr) { + unsigned rows = 0; + if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, rows) || + rows <= 0 || rows > 0x7fffffffu) + rows = kDefaultRows; + if (rows_ != rows) { + rows_ = rows; + if (GetLayoutObject()) { + GetLayoutObject() + ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + LayoutInvalidationReason::kAttributeChanged); + } + } + } else if (name == colsAttr) { + unsigned cols = 0; + if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, cols) || + cols <= 0 || cols > 0x7fffffffu) + cols = kDefaultCols; + if (cols_ != cols) { + cols_ = cols; + if (LayoutObject* layout_object = GetLayoutObject()) { + layout_object + ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + LayoutInvalidationReason::kAttributeChanged); + } + } + } else if (name == wrapAttr) { + // The virtual/physical values were a Netscape extension of HTML 3.0, now + // deprecated. The soft/hard /off values are a recommendation for HTML 4 + // extension by IE and NS 4. + WrapMethod wrap; + if (DeprecatedEqualIgnoringCase(value, "physical") || + DeprecatedEqualIgnoringCase(value, "hard") || + DeprecatedEqualIgnoringCase(value, "on")) + wrap = kHardWrap; + else if (DeprecatedEqualIgnoringCase(value, "off")) + wrap = kNoWrap; + else + wrap = kSoftWrap; + if (wrap != wrap_) { + wrap_ = wrap; + if (LayoutObject* layout_object = GetLayoutObject()) { + layout_object + ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + LayoutInvalidationReason::kAttributeChanged); + } + } + } else if (name == accesskeyAttr) { + // ignore for the moment + } else if (name == maxlengthAttr) { + UseCounter::Count(GetDocument(), WebFeature::kTextAreaMaxLength); + SetNeedsValidityCheck(); + } else if (name == minlengthAttr) { + UseCounter::Count(GetDocument(), WebFeature::kTextAreaMinLength); + SetNeedsValidityCheck(); + } else { + TextControlElement::ParseAttribute(params); + } +} + +LayoutObject* HTMLTextAreaElement::CreateLayoutObject(const ComputedStyle&) { + return new LayoutTextControlMultiLine(this); +} + +void HTMLTextAreaElement::AppendToFormData(FormData& form_data) { + if (GetName().IsEmpty()) + return; + + GetDocument().UpdateStyleAndLayout(); + + const String& text = + (wrap_ == kHardWrap) ? ValueWithHardLineBreaks() : value(); + form_data.append(GetName(), text); + + const AtomicString& dirname_attr_value = FastGetAttribute(dirnameAttr); + if (!dirname_attr_value.IsNull()) + form_data.append(dirname_attr_value, DirectionForFormData()); +} + +void HTMLTextAreaElement::ResetImpl() { + SetNonDirtyValue(defaultValue()); +} + +bool HTMLTextAreaElement::HasCustomFocusLogic() const { + return true; +} + +bool HTMLTextAreaElement::IsKeyboardFocusable() const { + // If a given text area can be focused at all, then it will always be keyboard + // focusable. + return IsFocusable(); +} + +bool HTMLTextAreaElement::ShouldShowFocusRingOnMouseFocus() const { + return true; +} + +void HTMLTextAreaElement::UpdateFocusAppearanceWithOptions( + SelectionBehaviorOnFocus selection_behavior, + const FocusOptions& options) { + switch (selection_behavior) { + case SelectionBehaviorOnFocus::kReset: // Fallthrough. + case SelectionBehaviorOnFocus::kRestore: + RestoreCachedSelection(); + break; + case SelectionBehaviorOnFocus::kNone: + return; + } + if (!options.preventScroll()) { + if (GetDocument().GetFrame()) + GetDocument().GetFrame()->Selection().RevealSelection(); + } +} + +void HTMLTextAreaElement::DefaultEventHandler(Event* event) { + if (GetLayoutObject() && (event->IsMouseEvent() || event->IsDragEvent() || + event->HasInterface(EventNames::WheelEvent) || + event->type() == EventTypeNames::blur)) + ForwardEvent(event); + else if (GetLayoutObject() && event->IsBeforeTextInsertedEvent()) + HandleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event)); + + TextControlElement::DefaultEventHandler(event); +} + +void HTMLTextAreaElement::SubtreeHasChanged() { +#if DCHECK_IS_ON() + // The innerEditor should have either Text nodes or a placeholder break + // element. If we see other nodes, it's a bug in editing code and we should + // fix it. + Element* inner_editor = InnerEditorElement(); + for (Node& node : NodeTraversal::DescendantsOf(*inner_editor)) { + if (node.IsTextNode()) + continue; + DCHECK(IsHTMLBRElement(node)); + DCHECK_EQ(&node, inner_editor->lastChild()); + } +#endif + AddPlaceholderBreakElementIfNecessary(); + SetValueBeforeFirstUserEditIfNotSet(); + UpdateValue(); + CheckIfValueWasReverted(value()); + SetNeedsValidityCheck(); + SetAutofilled(false); + UpdatePlaceholderVisibility(); + + if (!IsFocused()) + return; + + // When typing in a textarea, childrenChanged is not called, so we need to + // force the directionality check. + CalculateAndAdjustDirectionality(); + + DCHECK(GetDocument().IsActive()); + GetDocument().GetPage()->GetChromeClient().DidChangeValueInTextField(*this); +} + +void HTMLTextAreaElement::HandleBeforeTextInsertedEvent( + BeforeTextInsertedEvent* event) const { + DCHECK(event); + DCHECK(GetLayoutObject()); + int signed_max_length = maxLength(); + if (signed_max_length < 0) + return; + unsigned unsigned_max_length = static_cast<unsigned>(signed_max_length); + + const String& current_value = InnerEditorValue(); + unsigned current_length = ComputeLengthForAPIValue(current_value); + if (current_length + ComputeLengthForAPIValue(event->GetText()) < + unsigned_max_length) + return; + + // selectionLength represents the selection length of this text field to be + // removed by this insertion. + // If the text field has no focus, we don't need to take account of the + // selection length. The selection is the source of text drag-and-drop in + // that case, and nothing in the text field will be removed. + unsigned selection_length = 0; + if (IsFocused()) { + // TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets + // needs to be audited. See http://crbug.com/590369 for more details. + GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + + selection_length = ComputeLengthForAPIValue( + GetDocument().GetFrame()->Selection().SelectedText()); + } + DCHECK_GE(current_length, selection_length); + unsigned base_length = current_length - selection_length; + unsigned appendable_length = + unsigned_max_length > base_length ? unsigned_max_length - base_length : 0; + event->SetText(SanitizeUserInputValue(event->GetText(), appendable_length)); +} + +String HTMLTextAreaElement::SanitizeUserInputValue(const String& proposed_value, + unsigned max_length) { + unsigned submission_length = 0; + unsigned i = 0; + for (; i < proposed_value.length(); ++i) { + if (proposed_value[i] == '\r' && i + 1 < proposed_value.length() && + proposed_value[i + 1] == '\n') + continue; + ++submission_length; + if (submission_length == max_length) { + ++i; + break; + } + if (submission_length > max_length) + break; + } + if (i > 0 && U16_IS_LEAD(proposed_value[i - 1])) + --i; + return proposed_value.Left(i); +} + +void HTMLTextAreaElement::UpdateValue() { + value_ = InnerEditorValue(); + NotifyFormStateChanged(); + is_dirty_ = true; + UpdatePlaceholderVisibility(); +} + +String HTMLTextAreaElement::value() const { + return value_; +} + +void HTMLTextAreaElement::setValue(const String& value, + TextFieldEventBehavior event_behavior, + TextControlSetValueSelection selection) { + SetValueCommon(value, event_behavior, selection); + is_dirty_ = true; +} + +void HTMLTextAreaElement::SetNonDirtyValue(const String& value) { + SetValueCommon(value, kDispatchNoEvent, + TextControlSetValueSelection::kSetSelectionToEnd); + is_dirty_ = false; +} + +void HTMLTextAreaElement::SetValueCommon( + const String& new_value, + TextFieldEventBehavior event_behavior, + TextControlSetValueSelection selection) { + // Code elsewhere normalizes line endings added by the user via the keyboard + // or pasting. We normalize line endings coming from JavaScript here. + String normalized_value = new_value.IsNull() ? "" : new_value; + normalized_value.Replace("\r\n", "\n"); + normalized_value.Replace('\r', '\n'); + + // Clear the suggested value. Use the base class version to not trigger a view + // update. + TextControlElement::SetSuggestedValue(String()); + + // Return early because we don't want to trigger other side effects when the + // value isn't changing. This is interoperable. + if (normalized_value == value()) + return; + + if (event_behavior != kDispatchNoEvent) + SetValueBeforeFirstUserEditIfNotSet(); + value_ = normalized_value; + SetInnerEditorValue(value_); + if (event_behavior == kDispatchNoEvent) + SetLastChangeWasNotUserEdit(); + else + CheckIfValueWasReverted(value_); + UpdatePlaceholderVisibility(); + SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue)); + SetNeedsValidityCheck(); + if (IsFinishedParsingChildren() && + selection == TextControlSetValueSelection::kSetSelectionToEnd) { + // Set the caret to the end of the text value except for initialize. + unsigned end_of_string = value_.length(); + SetSelectionRange(end_of_string, end_of_string); + } + + NotifyFormStateChanged(); + switch (event_behavior) { + case kDispatchChangeEvent: + DispatchFormControlChangeEvent(); + break; + + case kDispatchInputAndChangeEvent: + DispatchInputEvent(); + DispatchFormControlChangeEvent(); + break; + + case kDispatchNoEvent: + break; + } +} + +String HTMLTextAreaElement::defaultValue() const { + StringBuilder value; + + // Since there may be comments, ignore nodes other than text nodes. + for (Node* n = firstChild(); n; n = n->nextSibling()) { + if (n->IsTextNode()) + value.Append(ToText(n)->data()); + } + + return value.ToString(); +} + +void HTMLTextAreaElement::setDefaultValue(const String& default_value) { + // To preserve comments, remove only the text nodes, then add a single text + // node. + HeapVector<Member<Node>> text_nodes; + for (Node* n = firstChild(); n; n = n->nextSibling()) { + if (n->IsTextNode()) + text_nodes.push_back(n); + } + for (const auto& text : text_nodes) + RemoveChild(text.Get(), IGNORE_EXCEPTION_FOR_TESTING); + + // Normalize line endings. + String value = default_value; + value.Replace("\r\n", "\n"); + value.Replace('\r', '\n'); + + InsertBefore(GetDocument().createTextNode(value), firstChild(), + IGNORE_EXCEPTION_FOR_TESTING); + + if (!is_dirty_) + SetNonDirtyValue(value); +} + +void HTMLTextAreaElement::SetSuggestedValue(const String& value) { + TextControlElement::SetSuggestedValue(value); + SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue)); +} + +String HTMLTextAreaElement::validationMessage() const { + if (!willValidate()) + return String(); + + if (CustomError()) + return CustomValidationMessage(); + + if (ValueMissing()) + return GetLocale().QueryString(WebLocalizedString::kValidationValueMissing); + + if (TooLong()) { + return GetLocale().ValidationMessageTooLongText(value().length(), + maxLength()); + } + + if (TooShort()) { + return GetLocale().ValidationMessageTooShortText(value().length(), + minLength()); + } + + return String(); +} + +bool HTMLTextAreaElement::ValueMissing() const { + // We should not call value() for performance. + return willValidate() && ValueMissing(nullptr); +} + +bool HTMLTextAreaElement::ValueMissing(const String* value) const { + return IsRequiredFormControl() && !IsDisabledOrReadOnly() && + (value ? *value : this->value()).IsEmpty(); +} + +bool HTMLTextAreaElement::TooLong() const { + // We should not call value() for performance. + return willValidate() && TooLong(nullptr, kCheckDirtyFlag); +} + +bool HTMLTextAreaElement::TooShort() const { + // We should not call value() for performance. + return willValidate() && TooShort(nullptr, kCheckDirtyFlag); +} + +bool HTMLTextAreaElement::TooLong(const String* value, + NeedsToCheckDirtyFlag check) const { + // Return false for the default value or value set by script even if it is + // longer than maxLength. + if (check == kCheckDirtyFlag && !LastChangeWasUserEdit()) + return false; + + int max = maxLength(); + if (max < 0) + return false; + unsigned len = + value ? ComputeLengthForAPIValue(*value) : this->value().length(); + return len > static_cast<unsigned>(max); +} + +bool HTMLTextAreaElement::TooShort(const String* value, + NeedsToCheckDirtyFlag check) const { + // Return false for the default value or value set by script even if it is + // shorter than minLength. + if (check == kCheckDirtyFlag && !LastChangeWasUserEdit()) + return false; + + int min = minLength(); + if (min <= 0) + return false; + // An empty string is excluded from minlength check. + unsigned len = + value ? ComputeLengthForAPIValue(*value) : this->value().length(); + return len > 0 && len < static_cast<unsigned>(min); +} + +bool HTMLTextAreaElement::IsValidValue(const String& candidate) const { + return !ValueMissing(&candidate) && !TooLong(&candidate, kIgnoreDirtyFlag) && + !TooShort(&candidate, kIgnoreDirtyFlag); +} + +void HTMLTextAreaElement::AccessKeyAction(bool) { + focus(); +} + +void HTMLTextAreaElement::setCols(unsigned cols) { + SetUnsignedIntegralAttribute(colsAttr, cols ? cols : kDefaultCols, + kDefaultCols); +} + +void HTMLTextAreaElement::setRows(unsigned rows) { + SetUnsignedIntegralAttribute(rowsAttr, rows ? rows : kDefaultRows, + kDefaultRows); +} + +bool HTMLTextAreaElement::MatchesReadOnlyPseudoClass() const { + return IsReadOnly(); +} + +bool HTMLTextAreaElement::MatchesReadWritePseudoClass() const { + return !IsReadOnly(); +} + +void HTMLTextAreaElement::SetPlaceholderVisibility(bool visible) { + is_placeholder_visible_ = visible; +} + +void HTMLTextAreaElement::UpdatePlaceholderText() { + HTMLElement* placeholder = PlaceholderElement(); + const String placeholder_text = GetPlaceholderValue(); + if (placeholder_text.IsEmpty()) { + if (placeholder) + UserAgentShadowRoot()->RemoveChild(placeholder); + return; + } + if (!placeholder) { + HTMLDivElement* new_element = HTMLDivElement::Create(GetDocument()); + placeholder = new_element; + placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder")); + placeholder->setAttribute(idAttr, ShadowElementNames::Placeholder()); + placeholder->SetInlineStyleProperty( + CSSPropertyDisplay, + IsPlaceholderVisible() ? CSSValueBlock : CSSValueNone, true); + UserAgentShadowRoot()->InsertBefore(placeholder, InnerEditorElement()); + } + placeholder->setTextContent(placeholder_text); +} + +String HTMLTextAreaElement::GetPlaceholderValue() const { + return !SuggestedValue().IsEmpty() ? SuggestedValue() + : FastGetAttribute(placeholderAttr); +} + +bool HTMLTextAreaElement::IsInteractiveContent() const { + return true; +} + +bool HTMLTextAreaElement::SupportsAutofocus() const { + return true; +} + +void HTMLTextAreaElement::CloneNonAttributePropertiesFrom( + const Element& source, + CloneChildrenFlag flag) { + const HTMLTextAreaElement& source_element = ToHTMLTextAreaElement(source); + SetValueCommon(source_element.value(), kDispatchNoEvent, + TextControlSetValueSelection::kSetSelectionToEnd); + is_dirty_ = source_element.is_dirty_; + TextControlElement::CloneNonAttributePropertiesFrom(source, flag); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h new file mode 100644 index 00000000000..9c4347d1733 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_TEXT_AREA_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_TEXT_AREA_ELEMENT_H_ + +#include "base/gtest_prod_util.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/text_control_element.h" + +namespace blink { + +class BeforeTextInsertedEvent; + +class CORE_EXPORT HTMLTextAreaElement final : public TextControlElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static HTMLTextAreaElement* Create(Document&); + + unsigned cols() const { return cols_; } + unsigned rows() const { return rows_; } + + bool ShouldWrapText() const { return wrap_ != kNoWrap; } + + String value() const override; + void setValue(const String&, + TextFieldEventBehavior = kDispatchNoEvent, + TextControlSetValueSelection = + TextControlSetValueSelection::kSetSelectionToEnd) override; + String defaultValue() const; + void setDefaultValue(const String&); + int textLength() const { return value().length(); } + + void SetSuggestedValue(const String& value) override; + + // For ValidityState + String validationMessage() const override; + bool ValueMissing() const override; + bool TooLong() const override; + bool TooShort() const override; + bool IsValidValue(const String&) const; + + void setCols(unsigned); + void setRows(unsigned); + + private: + FRIEND_TEST_ALL_PREFIXES(HTMLTextAreaElementTest, SanitizeUserInputValue); + explicit HTMLTextAreaElement(Document&); + + enum WrapMethod { kNoWrap, kSoftWrap, kHardWrap }; + + void DidAddUserAgentShadowRoot(ShadowRoot&) override; + // FIXME: Author shadows should be allowed + // https://bugs.webkit.org/show_bug.cgi?id=92608 + bool AreAuthorShadowsAllowed() const override { return false; } + + void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) const; + static String SanitizeUserInputValue(const String&, unsigned max_length); + void UpdateValue(); + void SetNonDirtyValue(const String&); + void SetValueCommon(const String&, + TextFieldEventBehavior, + TextControlSetValueSelection); + + bool IsPlaceholderVisible() const override { return is_placeholder_visible_; } + void SetPlaceholderVisibility(bool) override; + bool SupportsPlaceholder() const override { return true; } + String GetPlaceholderValue() const final; + void UpdatePlaceholderText() override; + bool IsEmptyValue() const override { return value().IsEmpty(); } + + bool IsOptionalFormControl() const override { + return !IsRequiredFormControl(); + } + bool IsRequiredFormControl() const override { return IsRequired(); } + + void DefaultEventHandler(Event*) override; + + void SubtreeHasChanged() override; + + bool IsEnumeratable() const override { return true; } + bool IsInteractiveContent() const override; + bool SupportsAutofocus() const override; + bool SupportLabels() const override { return true; } + + const AtomicString& FormControlType() const override; + + FormControlState SaveFormControlState() const override; + void RestoreFormControlState(const FormControlState&) override; + + bool IsTextControl() const override { return true; } + + void ChildrenChanged(const ChildrenChange&) override; + void ParseAttribute(const AttributeModificationParams&) override; + bool IsPresentationAttribute(const QualifiedName&) const override; + void CollectStyleForPresentationAttribute( + const QualifiedName&, + const AtomicString&, + MutableCSSPropertyValueSet*) override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + void AppendToFormData(FormData&) override; + void ResetImpl() override; + bool HasCustomFocusLogic() const override; + bool ShouldShowFocusRingOnMouseFocus() const override; + bool IsKeyboardFocusable() const override; + void UpdateFocusAppearanceWithOptions(SelectionBehaviorOnFocus, + const FocusOptions&) override; + + void AccessKeyAction(bool send_mouse_events) override; + + bool MatchesReadOnlyPseudoClass() const override; + bool MatchesReadWritePseudoClass() const override; + void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) final; + + // If the String* argument is 0, apply value(). + bool ValueMissing(const String*) const; + bool TooLong(const String*, NeedsToCheckDirtyFlag) const; + bool TooShort(const String*, NeedsToCheckDirtyFlag) const; + + unsigned rows_; + unsigned cols_; + WrapMethod wrap_; + mutable String value_; + mutable bool is_dirty_; + unsigned is_placeholder_visible_ : 1; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_HTML_TEXT_AREA_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl new file mode 100644 index 00000000000..5fc9a25ec0c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element.idl @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * Copyright (C) 2011 Motorola Mobility, Inc. 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. + */ + +// https://html.spec.whatwg.org/#the-textarea-element +[HTMLConstructor] +interface HTMLTextAreaElement : HTMLElement { + [CEReactions, Reflect] attribute DOMString autocomplete; + [CEReactions, Reflect] attribute boolean autofocus; + [CEReactions] attribute unsigned long cols; + [CEReactions, Reflect] attribute DOMString dirName; + [CEReactions, Reflect] attribute boolean disabled; + [ImplementedAs=formOwner] readonly attribute HTMLFormElement? form; + [CEReactions, RaisesException=Setter] attribute long maxLength; + [CEReactions, RaisesException=Setter] attribute long minLength; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString placeholder; + [CEReactions, Reflect] attribute boolean readOnly; + [CEReactions, Reflect] attribute boolean required; + [CEReactions] attribute unsigned long rows; + [CEReactions, Reflect] attribute DOMString wrap; + + readonly attribute DOMString type; + [CEReactions] attribute DOMString defaultValue; + [CEReactions] attribute [TreatNullAs=NullString] DOMString value; + readonly attribute unsigned long textLength; + + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); + + readonly attribute NodeList labels; + + void select(); + attribute unsigned long selectionStart; + attribute unsigned long selectionEnd; + attribute DOMString selectionDirection; + [RaisesException] void setRangeText(DOMString replacement); + [RaisesException] void setRangeText(DOMString replacement, + unsigned long start, + unsigned long end, + optional SelectionMode selectionMode = "preserve"); + [ImplementedAs=setSelectionRangeForBinding] + void setSelectionRange(unsigned long start, + unsigned long end, + optional DOMString direction); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc new file mode 100644 index 00000000000..1c2b730cd23 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/html_text_area_element_test.cc @@ -0,0 +1,36 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +TEST(HTMLTextAreaElementTest, SanitizeUserInputValue) { + UChar kLeadSurrogate = 0xD800; + EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("", 0)); + EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("a", 0)); + EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("\n", 0)); + StringBuilder builder; + builder.Append(kLeadSurrogate); + String lead_surrogate = builder.ToString(); + EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue(lead_surrogate, 0)); + + EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue("", 1)); + EXPECT_EQ("", HTMLTextAreaElement::SanitizeUserInputValue(lead_surrogate, 1)); + EXPECT_EQ("a", HTMLTextAreaElement::SanitizeUserInputValue("a", 1)); + EXPECT_EQ("\n", HTMLTextAreaElement::SanitizeUserInputValue("\n", 1)); + EXPECT_EQ("\n", HTMLTextAreaElement::SanitizeUserInputValue("\n", 2)); + + EXPECT_EQ("abc", HTMLTextAreaElement::SanitizeUserInputValue( + String("abc") + lead_surrogate, 4)); + EXPECT_EQ("a\ncd", HTMLTextAreaElement::SanitizeUserInputValue("a\ncdef", 4)); + EXPECT_EQ("a\rcd", HTMLTextAreaElement::SanitizeUserInputValue("a\rcdef", 4)); + EXPECT_EQ("a\r\ncd", + HTMLTextAreaElement::SanitizeUserInputValue("a\r\ncdef", 4)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc new file mode 100644 index 00000000000..fed797033fd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.cc @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + * rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * 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 "third_party/blink/renderer/core/html/forms/image_input_type.h" + +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/dom/sync_reattach_context.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/deprecation.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/html_image_fallback_helper.h" +#include "third_party/blink/renderer/core/html/html_image_loader.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" +#include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/layout_image.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +using namespace HTMLNames; + +inline ImageInputType::ImageInputType(HTMLInputElement& element) + : BaseButtonInputType(element), use_fallback_content_(false) {} + +InputType* ImageInputType::Create(HTMLInputElement& element) { + return new ImageInputType(element); +} + +const AtomicString& ImageInputType::FormControlType() const { + return InputTypeNames::image; +} + +bool ImageInputType::IsFormDataAppendable() const { + return true; +} + +void ImageInputType::AppendToFormData(FormData& form_data) const { + if (!GetElement().IsActivatedSubmit()) + return; + const AtomicString& name = GetElement().GetName(); + if (name.IsEmpty()) { + form_data.append("x", click_location_.X()); + form_data.append("y", click_location_.Y()); + return; + } + + DEFINE_STATIC_LOCAL(String, dot_x_string, (".x")); + DEFINE_STATIC_LOCAL(String, dot_y_string, (".y")); + form_data.append(name + dot_x_string, click_location_.X()); + form_data.append(name + dot_y_string, click_location_.Y()); + + if (!GetElement().value().IsEmpty()) { + Deprecation::CountDeprecation( + GetElement().GetDocument(), + WebFeature::kImageInputTypeFormDataWithNonEmptyValue); + form_data.append(name, GetElement().value()); + } +} + +String ImageInputType::ResultForDialogSubmit() const { + StringBuilder result; + result.AppendNumber(click_location_.X()); + result.Append(','); + result.AppendNumber(click_location_.Y()); + return result.ToString(); +} + +bool ImageInputType::SupportsValidation() const { + return false; +} + +static IntPoint ExtractClickLocation(Event* event) { + if (!event->UnderlyingEvent() || !event->UnderlyingEvent()->IsMouseEvent()) + return IntPoint(); + MouseEvent* mouse_event = ToMouseEvent(event->UnderlyingEvent()); + if (!mouse_event->HasPosition()) + return IntPoint(); + return IntPoint(mouse_event->offsetX(), mouse_event->offsetY()); +} + +void ImageInputType::HandleDOMActivateEvent(Event* event) { + if (GetElement().IsDisabledFormControl() || !GetElement().Form()) + return; + click_location_ = ExtractClickLocation(event); + GetElement().Form()->PrepareForSubmission( + event, &GetElement()); // Event handlers can run. + event->SetDefaultHandled(); +} + +LayoutObject* ImageInputType::CreateLayoutObject( + const ComputedStyle& style) const { + if (use_fallback_content_) + return new LayoutBlockFlow(&GetElement()); + LayoutImage* image = new LayoutImage(&GetElement()); + image->SetImageResource(LayoutImageResource::Create()); + return image; +} + +void ImageInputType::AltAttributeChanged() { + if (GetElement().UserAgentShadowRoot()) { + Element* text = + GetElement().UserAgentShadowRoot()->getElementById("alttext"); + String value = GetElement().AltText(); + if (text && text->textContent() != value) + text->setTextContent(GetElement().AltText()); + } +} + +void ImageInputType::SrcAttributeChanged() { + if (!GetElement().GetLayoutObject()) + return; + GetElement().EnsureImageLoader().UpdateFromElement( + ImageLoader::kUpdateIgnorePreviousError); +} + +void ImageInputType::ValueAttributeChanged() { + if (use_fallback_content_) + return; + BaseButtonInputType::ValueAttributeChanged(); +} + +void ImageInputType::StartResourceLoading() { + BaseButtonInputType::StartResourceLoading(); + + HTMLImageLoader& image_loader = GetElement().EnsureImageLoader(); + image_loader.UpdateFromElement(); + + LayoutObject* layout_object = GetElement().GetLayoutObject(); + if (!layout_object || !layout_object->IsLayoutImage()) + return; + + LayoutImageResource* image_resource = + ToLayoutImage(layout_object)->ImageResource(); + image_resource->SetImageResource(image_loader.GetContent()); +} + +bool ImageInputType::ShouldRespectAlignAttribute() { + return true; +} + +bool ImageInputType::CanBeSuccessfulSubmitButton() { + return true; +} + +bool ImageInputType::IsEnumeratable() { + return false; +} + +bool ImageInputType::ShouldRespectHeightAndWidthAttributes() { + return true; +} + +unsigned ImageInputType::Height() const { + if (!GetElement().GetLayoutObject()) { + // Check the attribute first for an explicit pixel value. + unsigned height; + if (ParseHTMLNonNegativeInteger(GetElement().FastGetAttribute(heightAttr), + height)) + return height; + + // If the image is available, use its height. + HTMLImageLoader* image_loader = GetElement().ImageLoader(); + if (image_loader && image_loader->GetContent()) { + return image_loader->GetContent() + ->IntrinsicSize(LayoutObject::ShouldRespectImageOrientation(nullptr)) + .Height(); + } + } + + GetElement().GetDocument().UpdateStyleAndLayout(); + + LayoutBox* box = GetElement().GetLayoutBox(); + return box ? AdjustForAbsoluteZoom::AdjustInt(box->ContentHeight().ToInt(), + box) + : 0; +} + +unsigned ImageInputType::Width() const { + if (!GetElement().GetLayoutObject()) { + // Check the attribute first for an explicit pixel value. + unsigned width; + if (ParseHTMLNonNegativeInteger(GetElement().FastGetAttribute(widthAttr), + width)) + return width; + + // If the image is available, use its width. + HTMLImageLoader* image_loader = GetElement().ImageLoader(); + if (image_loader && image_loader->GetContent()) { + return image_loader->GetContent() + ->IntrinsicSize(LayoutObject::ShouldRespectImageOrientation(nullptr)) + .Width(); + } + } + + GetElement().GetDocument().UpdateStyleAndLayout(); + + LayoutBox* box = GetElement().GetLayoutBox(); + return box ? AdjustForAbsoluteZoom::AdjustInt(box->ContentWidth().ToInt(), + box) + : 0; +} + +bool ImageInputType::HasLegalLinkAttribute(const QualifiedName& name) const { + return name == srcAttr || BaseButtonInputType::HasLegalLinkAttribute(name); +} + +const QualifiedName& ImageInputType::SubResourceAttributeName() const { + return srcAttr; +} + +void ImageInputType::EnsureFallbackContent() { + if (use_fallback_content_) + return; + SetUseFallbackContent(); + ReattachFallbackContent(); +} + +void ImageInputType::SetUseFallbackContent() { + if (use_fallback_content_) + return; + use_fallback_content_ = true; + if (GetElement().GetDocument().InStyleRecalc()) + return; + if (ShadowRoot* root = GetElement().UserAgentShadowRoot()) + root->RemoveChildren(); + CreateShadowSubtree(); +} + +void ImageInputType::EnsurePrimaryContent() { + if (!use_fallback_content_) + return; + use_fallback_content_ = false; + if (ShadowRoot* root = GetElement().UserAgentShadowRoot()) + root->RemoveChildren(); + CreateShadowSubtree(); + ReattachFallbackContent(); +} + +void ImageInputType::ReattachFallbackContent() { + if (GetElement().GetDocument().InStyleRecalc()) { + // This can happen inside of AttachLayoutTree() in the middle of a + // RebuildLayoutTree, so we need to reattach synchronously here. + GetElement().ReattachLayoutTree( + SyncReattachContext::CurrentAttachContext()); + } else { + GetElement().LazyReattachIfAttached(); + } +} + +void ImageInputType::CreateShadowSubtree() { + if (!use_fallback_content_) { + BaseButtonInputType::CreateShadowSubtree(); + return; + } + HTMLImageFallbackHelper::CreateAltTextShadowTree(GetElement()); +} + +scoped_refptr<ComputedStyle> ImageInputType::CustomStyleForLayoutObject( + scoped_refptr<ComputedStyle> new_style) { + if (!use_fallback_content_) + return new_style; + + return HTMLImageFallbackHelper::CustomStyleForAltText(GetElement(), + std::move(new_style)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h new file mode 100644 index 00000000000..13f721c24e0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/image_input_type.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * Copyright (C) 2012 Samsung Electronics. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_IMAGE_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_IMAGE_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h" +#include "third_party/blink/renderer/platform/geometry/int_point.h" + +namespace blink { + +class ImageInputType final : public BaseButtonInputType { + public: + static InputType* Create(HTMLInputElement&); + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject( + scoped_refptr<ComputedStyle>) override; + + private: + ImageInputType(HTMLInputElement&); + const AtomicString& FormControlType() const override; + bool IsFormDataAppendable() const override; + void AppendToFormData(FormData&) const override; + String ResultForDialogSubmit() const override; + bool SupportsValidation() const override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + void HandleDOMActivateEvent(Event*) override; + void AltAttributeChanged() override; + void SrcAttributeChanged() override; + void ValueAttributeChanged() override; + void StartResourceLoading() override; + bool ShouldRespectAlignAttribute() override; + bool CanBeSuccessfulSubmitButton() override; + bool IsEnumeratable() override; + bool ShouldRespectHeightAndWidthAttributes() override; + unsigned Height() const override; + unsigned Width() const override; + bool HasLegalLinkAttribute(const QualifiedName&) const override; + const QualifiedName& SubResourceAttributeName() const override; + void EnsureFallbackContent() override; + void EnsurePrimaryContent() override; + void CreateShadowSubtree() override; + + void ReattachFallbackContent(); + void SetUseFallbackContent(); + bool HasFallbackContent() const override { return use_fallback_content_; } + + // Valid only during HTMLFormElement::prepareForSubmission(). + IntPoint click_location_; + + bool use_fallback_content_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_IMAGE_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/input_type.cc new file mode 100644 index 00000000000..6e0ad2c33b7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type.cc @@ -0,0 +1,910 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + * rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) + * Copyright (C) 2009, 2010, 2011, 2012 Google Inc. All rights reserved. + * 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 "third_party/blink/renderer/core/html/forms/input_type.h" + +#include <limits> +#include <memory> +#include <utility> + +#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/exception_code.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/fileapi/file_list.h" +#include "third_party/blink/renderer/core/html/forms/button_input_type.h" +#include "third_party/blink/renderer/core/html/forms/checkbox_input_type.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser.h" +#include "third_party/blink/renderer/core/html/forms/color_input_type.h" +#include "third_party/blink/renderer/core/html/forms/date_input_type.h" +#include "third_party/blink/renderer/core/html/forms/date_time_local_input_type.h" +#include "third_party/blink/renderer/core/html/forms/email_input_type.h" +#include "third_party/blink/renderer/core/html/forms/file_input_type.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/hidden_input_type.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/image_input_type.h" +#include "third_party/blink/renderer/core/html/forms/month_input_type.h" +#include "third_party/blink/renderer/core/html/forms/number_input_type.h" +#include "third_party/blink/renderer/core/html/forms/password_input_type.h" +#include "third_party/blink/renderer/core/html/forms/radio_input_type.h" +#include "third_party/blink/renderer/core/html/forms/range_input_type.h" +#include "third_party/blink/renderer/core/html/forms/reset_input_type.h" +#include "third_party/blink/renderer/core/html/forms/search_input_type.h" +#include "third_party/blink/renderer/core/html/forms/submit_input_type.h" +#include "third_party/blink/renderer/core/html/forms/telephone_input_type.h" +#include "third_party/blink/renderer/core/html/forms/text_input_type.h" +#include "third_party/blink/renderer/core/html/forms/time_input_type.h" +#include "third_party/blink/renderer/core/html/forms/url_input_type.h" +#include "third_party/blink/renderer/core/html/forms/week_input_type.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/json/json_values.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/text/text_break_iterator.h" + +namespace blink { + +using blink::WebLocalizedString; +using namespace HTMLNames; + +using InputTypeFactoryFunction = InputType* (*)(HTMLInputElement&); +using InputTypeFactoryMap = HashMap<AtomicString, InputTypeFactoryFunction>; + +static std::unique_ptr<InputTypeFactoryMap> CreateInputTypeFactoryMap() { + std::unique_ptr<InputTypeFactoryMap> map = + std::make_unique<InputTypeFactoryMap>(); + map->insert(InputTypeNames::button, ButtonInputType::Create); + map->insert(InputTypeNames::checkbox, CheckboxInputType::Create); + map->insert(InputTypeNames::color, ColorInputType::Create); + map->insert(InputTypeNames::date, DateInputType::Create); + map->insert(InputTypeNames::datetime_local, DateTimeLocalInputType::Create); + map->insert(InputTypeNames::email, EmailInputType::Create); + map->insert(InputTypeNames::file, FileInputType::Create); + map->insert(InputTypeNames::hidden, HiddenInputType::Create); + map->insert(InputTypeNames::image, ImageInputType::Create); + map->insert(InputTypeNames::month, MonthInputType::Create); + map->insert(InputTypeNames::number, NumberInputType::Create); + map->insert(InputTypeNames::password, PasswordInputType::Create); + map->insert(InputTypeNames::radio, RadioInputType::Create); + map->insert(InputTypeNames::range, RangeInputType::Create); + map->insert(InputTypeNames::reset, ResetInputType::Create); + map->insert(InputTypeNames::search, SearchInputType::Create); + map->insert(InputTypeNames::submit, SubmitInputType::Create); + map->insert(InputTypeNames::tel, TelephoneInputType::Create); + map->insert(InputTypeNames::time, TimeInputType::Create); + map->insert(InputTypeNames::url, URLInputType::Create); + map->insert(InputTypeNames::week, WeekInputType::Create); + // No need to register "text" because it is the default type. + return map; +} + +static const InputTypeFactoryMap* FactoryMap() { + static const InputTypeFactoryMap* factory_map = + CreateInputTypeFactoryMap().release(); + return factory_map; +} + +InputType* InputType::Create(HTMLInputElement& element, + const AtomicString& type_name) { + InputTypeFactoryFunction factory = + type_name.IsEmpty() ? nullptr : FactoryMap()->at(type_name); + if (!factory) + factory = TextInputType::Create; + return factory(element); +} + +InputType* InputType::CreateText(HTMLInputElement& element) { + return TextInputType::Create(element); +} + +const AtomicString& InputType::NormalizeTypeName( + const AtomicString& type_name) { + if (type_name.IsEmpty()) + return InputTypeNames::text; + InputTypeFactoryMap::const_iterator it = + FactoryMap()->find(type_name.LowerASCII()); + return it == FactoryMap()->end() ? InputTypeNames::text : it->key; +} + +InputType::~InputType() = default; + +void InputType::Trace(blink::Visitor* visitor) { + visitor->Trace(element_); +} + +bool InputType::IsTextField() const { + return false; +} + +bool InputType::ShouldSaveAndRestoreFormControlState() const { + return true; +} + +bool InputType::IsFormDataAppendable() const { + // There is no form data unless there's a name for non-image types. + return !GetElement().GetName().IsEmpty(); +} + +void InputType::AppendToFormData(FormData& form_data) const { + form_data.append(GetElement().GetName(), GetElement().value()); +} + +String InputType::ResultForDialogSubmit() const { + return GetElement().FastGetAttribute(valueAttr); +} + +double InputType::ValueAsDate() const { + return DateComponents::InvalidMilliseconds(); +} + +void InputType::SetValueAsDate(double, ExceptionState& exception_state) const { + exception_state.ThrowDOMException( + kInvalidStateError, "This input element does not support Date values."); +} + +double InputType::ValueAsDouble() const { + return std::numeric_limits<double>::quiet_NaN(); +} + +void InputType::SetValueAsDouble(double double_value, + TextFieldEventBehavior event_behavior, + ExceptionState& exception_state) const { + exception_state.ThrowDOMException( + kInvalidStateError, "This input element does not support Number values."); +} + +void InputType::SetValueAsDecimal(const Decimal& new_value, + TextFieldEventBehavior event_behavior, + ExceptionState&) const { + GetElement().setValue(Serialize(new_value), event_behavior); +} + +void InputType::ReadingChecked() const {} + +bool InputType::SupportsValidation() const { + return true; +} + +bool InputType::TypeMismatchFor(const String&) const { + return false; +} + +bool InputType::TypeMismatch() const { + return false; +} + +bool InputType::SupportsRequired() const { + // Almost all validatable types support @required. + return SupportsValidation(); +} + +bool InputType::ValueMissing(const String&) const { + return false; +} + +bool InputType::TooLong(const String&, + TextControlElement::NeedsToCheckDirtyFlag) const { + return false; +} + +bool InputType::TooShort(const String&, + TextControlElement::NeedsToCheckDirtyFlag) const { + return false; +} + +bool InputType::PatternMismatch(const String&) const { + return false; +} + +bool InputType::RangeUnderflow(const String& value) const { + if (!IsSteppable()) + return false; + + const Decimal numeric_value = ParseToNumberOrNaN(value); + if (!numeric_value.IsFinite()) + return false; + + return numeric_value < CreateStepRange(kRejectAny).Minimum(); +} + +bool InputType::RangeOverflow(const String& value) const { + if (!IsSteppable()) + return false; + + const Decimal numeric_value = ParseToNumberOrNaN(value); + if (!numeric_value.IsFinite()) + return false; + + return numeric_value > CreateStepRange(kRejectAny).Maximum(); +} + +Decimal InputType::DefaultValueForStepUp() const { + return 0; +} + +double InputType::Minimum() const { + return CreateStepRange(kRejectAny).Minimum().ToDouble(); +} + +double InputType::Maximum() const { + return CreateStepRange(kRejectAny).Maximum().ToDouble(); +} + +bool InputType::IsInRange(const String& value) const { + if (!IsSteppable()) + return false; + + // This function should return true if both of validity.rangeUnderflow and + // validity.rangeOverflow are false. + // If the INPUT has no value, they are false. + const Decimal numeric_value = ParseToNumberOrNaN(value); + if (!numeric_value.IsFinite()) + return true; + + StepRange step_range(CreateStepRange(kRejectAny)); + return step_range.HasRangeLimitations() && + numeric_value >= step_range.Minimum() && + numeric_value <= step_range.Maximum(); +} + +bool InputType::IsOutOfRange(const String& value) const { + if (!IsSteppable()) + return false; + + // This function should return true if either validity.rangeUnderflow or + // validity.rangeOverflow are true. + // If the INPUT has no value, they are false. + const Decimal numeric_value = ParseToNumberOrNaN(value); + if (!numeric_value.IsFinite()) + return false; + + StepRange step_range(CreateStepRange(kRejectAny)); + return step_range.HasRangeLimitations() && + (numeric_value < step_range.Minimum() || + numeric_value > step_range.Maximum()); +} + +void InputType::InRangeChanged() const { + if (IsSteppable()) { + GetElement().PseudoStateChanged(CSSSelector::kPseudoInRange); + GetElement().PseudoStateChanged(CSSSelector::kPseudoOutOfRange); + } +} + +bool InputType::StepMismatch(const String& value) const { + if (!IsSteppable()) + return false; + + const Decimal numeric_value = ParseToNumberOrNaN(value); + if (!numeric_value.IsFinite()) + return false; + + return CreateStepRange(kRejectAny).StepMismatch(numeric_value); +} + +String InputType::BadInputText() const { + NOTREACHED(); + return GetLocale().QueryString(WebLocalizedString::kValidationTypeMismatch); +} + +String InputType::RangeOverflowText(const Decimal&) const { + NOTREACHED(); + return String(); +} + +String InputType::RangeUnderflowText(const Decimal&) const { + NOTREACHED(); + return String(); +} + +String InputType::TypeMismatchText() const { + return GetLocale().QueryString(WebLocalizedString::kValidationTypeMismatch); +} + +String InputType::ValueMissingText() const { + return GetLocale().QueryString(WebLocalizedString::kValidationValueMissing); +} + +std::pair<String, String> InputType::ValidationMessage( + const InputTypeView& input_type_view) const { + const String value = GetElement().value(); + + // The order of the following checks is meaningful. e.g. We'd like to show the + // badInput message even if the control has other validation errors. + if (input_type_view.HasBadInput()) + return std::make_pair(BadInputText(), g_empty_string); + + if (ValueMissing(value)) + return std::make_pair(ValueMissingText(), g_empty_string); + + if (TypeMismatch()) + return std::make_pair(TypeMismatchText(), g_empty_string); + + if (PatternMismatch(value)) { + // https://html.spec.whatwg.org/multipage/forms.html#attr-input-pattern + // When an input element has a pattern attribute specified, authors + // should include a title attribute to give a description of the + // pattern. User agents may use the contents of this attribute, if it + // is present, when informing the user that the pattern is not matched + return std::make_pair( + GetLocale().QueryString(WebLocalizedString::kValidationPatternMismatch), + GetElement().FastGetAttribute(titleAttr).GetString()); + } + + if (GetElement().TooLong()) { + return std::make_pair(GetLocale().ValidationMessageTooLongText( + value.length(), GetElement().maxLength()), + g_empty_string); + } + + if (GetElement().TooShort()) { + return std::make_pair(GetLocale().ValidationMessageTooShortText( + value.length(), GetElement().minLength()), + g_empty_string); + } + + if (!IsSteppable()) + return std::make_pair(g_empty_string, g_empty_string); + + const Decimal numeric_value = ParseToNumberOrNaN(value); + if (!numeric_value.IsFinite()) + return std::make_pair(g_empty_string, g_empty_string); + + StepRange step_range(CreateStepRange(kRejectAny)); + + if (numeric_value < step_range.Minimum()) + return std::make_pair(RangeUnderflowText(step_range.Minimum()), + g_empty_string); + + if (numeric_value > step_range.Maximum()) + return std::make_pair(RangeOverflowText(step_range.Maximum()), + g_empty_string); + + if (step_range.StepMismatch(numeric_value)) { + DCHECK(step_range.HasStep()); + Decimal candidate1 = step_range.ClampValue(numeric_value); + String localized_candidate1 = LocalizeValue(Serialize(candidate1)); + Decimal candidate2 = candidate1 < numeric_value + ? candidate1 + step_range.Step() + : candidate1 - step_range.Step(); + if (!candidate2.IsFinite() || candidate2 < step_range.Minimum() || + candidate2 > step_range.Maximum()) { + return std::make_pair( + GetLocale().QueryString( + WebLocalizedString::kValidationStepMismatchCloseToLimit, + localized_candidate1), + g_empty_string); + } + String localized_candidate2 = LocalizeValue(Serialize(candidate2)); + if (candidate1 < candidate2) { + return std::make_pair( + GetLocale().QueryString(WebLocalizedString::kValidationStepMismatch, + localized_candidate1, localized_candidate2), + g_empty_string); + } + return std::make_pair( + GetLocale().QueryString(WebLocalizedString::kValidationStepMismatch, + localized_candidate2, localized_candidate1), + g_empty_string); + } + + return std::make_pair(g_empty_string, g_empty_string); +} + +Decimal InputType::ParseToNumber(const String&, + const Decimal& default_value) const { + NOTREACHED(); + return default_value; +} + +Decimal InputType::ParseToNumberOrNaN(const String& string) const { + return ParseToNumber(string, Decimal::Nan()); +} + +String InputType::Serialize(const Decimal&) const { + NOTREACHED(); + return String(); +} + +ChromeClient* InputType::GetChromeClient() const { + if (Page* page = GetElement().GetDocument().GetPage()) + return &page->GetChromeClient(); + return nullptr; +} + +Locale& InputType::GetLocale() const { + return GetElement().GetLocale(); +} + +bool InputType::CanSetStringValue() const { + return true; +} + +bool InputType::IsKeyboardFocusable() const { + return GetElement().IsFocusable(); +} + +bool InputType::ShouldShowFocusRingOnMouseFocus() const { + return false; +} + +void InputType::CountUsage() {} + +bool InputType::ShouldRespectAlignAttribute() { + return false; +} + +void InputType::SanitizeValueInResponseToMinOrMaxAttributeChange() {} + +bool InputType::CanBeSuccessfulSubmitButton() { + return false; +} + +bool InputType::MatchesDefaultPseudoClass() { + return false; +} + +bool InputType::LayoutObjectIsNeeded() { + return true; +} + +FileList* InputType::Files() { + return nullptr; +} + +void InputType::SetFiles(FileList*) {} + +void InputType::SetFilesFromPaths(const Vector<String>& paths) {} + +String InputType::ValueInFilenameValueMode() const { + NOTREACHED(); + return String(); +} + +String InputType::DefaultLabel() const { + return String(); +} + +bool InputType::CanSetSuggestedValue() { + return false; +} + +bool InputType::ShouldSendChangeEventAfterCheckedChanged() { + return true; +} + +void InputType::DispatchSearchEvent() {} + +void InputType::SetValue(const String& sanitized_value, + bool value_changed, + TextFieldEventBehavior event_behavior, + TextControlSetValueSelection) { + // This setValue() implementation is used only for ValueMode::kValue except + // TextFieldInputType. That is to say, type=color, type=range, and temporal + // input types. + DCHECK_EQ(GetValueMode(), ValueMode::kValue); + if (event_behavior == kDispatchNoEvent) + GetElement().SetNonAttributeValue(sanitized_value); + else + GetElement().SetNonAttributeValueByUserEdit(sanitized_value); + if (!value_changed) + return; + switch (event_behavior) { + case kDispatchChangeEvent: + GetElement().DispatchFormControlChangeEvent(); + break; + case kDispatchInputAndChangeEvent: + GetElement().DispatchInputEvent(); + GetElement().DispatchFormControlChangeEvent(); + break; + case kDispatchNoEvent: + break; + } +} + +bool InputType::CanSetValue(const String&) { + return true; +} + +String InputType::LocalizeValue(const String& proposed_value) const { + return proposed_value; +} + +String InputType::VisibleValue() const { + return GetElement().value(); +} + +String InputType::SanitizeValue(const String& proposed_value) const { + return proposed_value; +} + +String InputType::SanitizeUserInputValue(const String& proposed_value) const { + return SanitizeValue(proposed_value); +} + +void InputType::WarnIfValueIsInvalidAndElementIsVisible( + const String& value) const { + // Don't warn if the value is set in Modernizr. + const ComputedStyle* style = GetElement().GetComputedStyle(); + if (style && style->Visibility() != EVisibility::kHidden) + WarnIfValueIsInvalid(value); +} + +void InputType::WarnIfValueIsInvalid(const String&) const {} + +bool InputType::ReceiveDroppedFiles(const DragData*) { + NOTREACHED(); + return false; +} + +String InputType::DroppedFileSystemId() { + NOTREACHED(); + return String(); +} + +bool InputType::ShouldRespectListAttribute() { + return false; +} + +bool InputType::IsTextButton() const { + return false; +} + +bool InputType::IsInteractiveContent() const { + return true; +} + +bool InputType::IsEnumeratable() { + return true; +} + +bool InputType::IsCheckable() { + return false; +} + +bool InputType::IsSteppable() const { + return false; +} + +bool InputType::ShouldRespectHeightAndWidthAttributes() { + return false; +} + +int InputType::MaxLength() const { + return -1; +} + +int InputType::MinLength() const { + return 0; +} + +bool InputType::SupportsPlaceholder() const { + return false; +} + +bool InputType::SupportsReadOnly() const { + return false; +} + +String InputType::DefaultToolTip(const InputTypeView& input_type_view) const { + if (GetElement().Form() && GetElement().Form()->NoValidate()) + return String(); + return ValidationMessage(input_type_view).first; +} + +Decimal InputType::FindClosestTickMarkValue(const Decimal&) { + NOTREACHED(); + return Decimal::Nan(); +} + +bool InputType::HasLegalLinkAttribute(const QualifiedName&) const { + return false; +} + +const QualifiedName& InputType::SubResourceAttributeName() const { + return QualifiedName::Null(); +} + +void InputType::CopyNonAttributeProperties(const HTMLInputElement&) {} + +void InputType::OnAttachWithLayoutObject() {} + +void InputType::OnDetachWithLayoutObject() {} + +bool InputType::ShouldAppearIndeterminate() const { + return false; +} + +bool InputType::SupportsInputModeAttribute() const { + return false; +} + +bool InputType::SupportsSelectionAPI() const { + return false; +} + +unsigned InputType::Height() const { + return 0; +} + +unsigned InputType::Width() const { + return 0; +} + +ColorChooserClient* InputType::GetColorChooserClient() { + return nullptr; +} + +void InputType::ApplyStep(const Decimal& current, + double count, + AnyStepHandling any_step_handling, + TextFieldEventBehavior event_behavior, + ExceptionState& exception_state) { + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-stepup + + StepRange step_range(CreateStepRange(any_step_handling)); + // 2. If the element has no allowed value step, then throw an + // InvalidStateError exception, and abort these steps. + if (!step_range.HasStep()) { + exception_state.ThrowDOMException( + kInvalidStateError, + "This form element does not have an allowed value step."); + return; + } + + // 3. If the element has a minimum and a maximum and the minimum is greater + // than the maximum, then abort these steps. + if (step_range.Minimum() > step_range.Maximum()) + return; + + // 4. If the element has a minimum and a maximum and there is no value + // greater than or equal to the element's minimum and less than or equal to + // the element's maximum that, when subtracted from the step base, is an + // integral multiple of the allowed value step, then abort these steps. + Decimal aligned_maximum = step_range.StepSnappedMaximum(); + if (!aligned_maximum.IsFinite()) + return; + + Decimal base = step_range.StepBase(); + Decimal step = step_range.Step(); + EventQueueScope scope; + Decimal new_value = current; + const AtomicString& step_string = GetElement().FastGetAttribute(stepAttr); + if (!DeprecatedEqualIgnoringCase(step_string, "any") && + step_range.StepMismatch(current)) { + // Snap-to-step / clamping steps + // If the current value is not matched to step value: + // - The value should be the larger matched value nearest to 0 if count > 0 + // e.g. <input type=number value=3 min=-100 step=3> -> 5 + // - The value should be the smaller matched value nearest to 0 if count < 0 + // e.g. <input type=number value=3 min=-100 step=3> -> 2 + // + + DCHECK(!step.IsZero()); + if (count < 0) { + new_value = base + ((new_value - base) / step).Floor() * step; + ++count; + } else if (count > 0) { + new_value = base + ((new_value - base) / step).Ceil() * step; + --count; + } + } + new_value = new_value + step_range.Step() * Decimal::FromDouble(count); + + if (!DeprecatedEqualIgnoringCase(step_string, "any")) + new_value = step_range.AlignValueForStep(current, new_value); + + // 7. If the element has a minimum, and value is less than that minimum, + // then set value to the smallest value that, when subtracted from the step + // base, is an integral multiple of the allowed value step, and that is more + // than or equal to minimum. + if (new_value < step_range.Minimum()) { + const Decimal aligned_minimum = + base + ((step_range.Minimum() - base) / step).Ceil() * step; + DCHECK_GE(aligned_minimum, step_range.Minimum()); + new_value = aligned_minimum; + } + + // 8. If the element has a maximum, and value is greater than that maximum, + // then set value to the largest value that, when subtracted from the step + // base, is an integral multiple of the allowed value step, and that is less + // than or equal to maximum. + if (new_value > step_range.Maximum()) + new_value = aligned_maximum; + + // 9. Let value as string be the result of running the algorithm to convert + // a number to a string, as defined for the input element's type attribute's + // current state, on value. + // 10. Set the value of the element to value as string. + SetValueAsDecimal(new_value, event_behavior, exception_state); + + if (AXObjectCache* cache = GetElement().GetDocument().ExistingAXObjectCache()) + cache->HandleValueChanged(&GetElement()); +} + +bool InputType::GetAllowedValueStep(Decimal* step) const { + StepRange step_range(CreateStepRange(kRejectAny)); + *step = step_range.Step(); + return step_range.HasStep(); +} + +StepRange InputType::CreateStepRange(AnyStepHandling) const { + NOTREACHED(); + return StepRange(); +} + +void InputType::StepUp(double n, ExceptionState& exception_state) { + if (!IsSteppable()) { + exception_state.ThrowDOMException(kInvalidStateError, + "This form element is not steppable."); + return; + } + const Decimal current = ParseToNumber(GetElement().value(), 0); + ApplyStep(current, n, kRejectAny, kDispatchNoEvent, exception_state); +} + +void InputType::StepUpFromLayoutObject(int n) { + // The only difference from stepUp()/stepDown() is the extra treatment + // of the current value before applying the step: + // + // If the current value is not a number, including empty, the current value is + // assumed as 0. + // * If 0 is in-range, and matches to step value + // - The value should be the +step if n > 0 + // - The value should be the -step if n < 0 + // If -step or +step is out of range, new value should be 0. + // * If 0 is smaller than the minimum value + // - The value should be the minimum value for any n + // * If 0 is larger than the maximum value + // - The value should be the maximum value for any n + // * If 0 is in-range, but not matched to step value + // - The value should be the larger matched value nearest to 0 if n > 0 + // e.g. <input type=number min=-100 step=3> -> 2 + // - The value should be the smaler matched value nearest to 0 if n < 0 + // e.g. <input type=number min=-100 step=3> -> -1 + // As for date/datetime-local/month/time/week types, the current value is + // assumed as "the current local date/time". + // As for datetime type, the current value is assumed as "the current + // date/time in UTC". + // If the current value is smaller than the minimum value: + // - The value should be the minimum value if n > 0 + // - Nothing should happen if n < 0 + // If the current value is larger than the maximum value: + // - The value should be the maximum value if n < 0 + // - Nothing should happen if n > 0 + // + // n is assumed as -n if step < 0. + + DCHECK(IsSteppable()); + if (!IsSteppable()) + return; + DCHECK(n); + if (!n) + return; + + StepRange step_range(CreateStepRange(kAnyIsDefaultStep)); + + // FIXME: Not any changes after stepping, even if it is an invalid value, may + // be better. + // (e.g. Stepping-up for <input type="number" value="foo" step="any" /> => + // "foo") + if (!step_range.HasStep()) + return; + + EventQueueScope scope; + const Decimal step = step_range.Step(); + + int sign; + if (step > 0) + sign = n; + else if (step < 0) + sign = -n; + else + sign = 0; + + Decimal current = ParseToNumberOrNaN(GetElement().value()); + if (!current.IsFinite()) { + current = DefaultValueForStepUp(); + const Decimal next_diff = step * n; + if (current < step_range.Minimum() - next_diff) + current = step_range.Minimum() - next_diff; + if (current > step_range.Maximum() - next_diff) + current = step_range.Maximum() - next_diff; + SetValueAsDecimal(current, kDispatchNoEvent, IGNORE_EXCEPTION_FOR_TESTING); + } + if ((sign > 0 && current < step_range.Minimum()) || + (sign < 0 && current > step_range.Maximum())) { + SetValueAsDecimal(sign > 0 ? step_range.Minimum() : step_range.Maximum(), + kDispatchChangeEvent, IGNORE_EXCEPTION_FOR_TESTING); + return; + } + if ((sign > 0 && current >= step_range.Maximum()) || + (sign < 0 && current <= step_range.Minimum())) + return; + ApplyStep(current, n, kAnyIsDefaultStep, kDispatchChangeEvent, + IGNORE_EXCEPTION_FOR_TESTING); +} + +void InputType::CountUsageIfVisible(WebFeature feature) const { + if (const ComputedStyle* style = GetElement().GetComputedStyle()) { + if (style->Visibility() != EVisibility::kHidden) + UseCounter::Count(GetElement().GetDocument(), feature); + } +} + +Decimal InputType::FindStepBase(const Decimal& default_value) const { + Decimal step_base = + ParseToNumber(GetElement().FastGetAttribute(minAttr), Decimal::Nan()); + if (!step_base.IsFinite()) + step_base = + ParseToNumber(GetElement().FastGetAttribute(valueAttr), default_value); + return step_base; +} + +StepRange InputType::CreateStepRange( + AnyStepHandling any_step_handling, + const Decimal& step_base_default, + const Decimal& minimum_default, + const Decimal& maximum_default, + const StepRange::StepDescription& step_description) const { + bool has_range_limitations = false; + const Decimal step_base = FindStepBase(step_base_default); + Decimal minimum = ParseToNumberOrNaN(GetElement().FastGetAttribute(minAttr)); + if (minimum.IsFinite()) + has_range_limitations = true; + else + minimum = minimum_default; + Decimal maximum = ParseToNumberOrNaN(GetElement().FastGetAttribute(maxAttr)); + if (maximum.IsFinite()) + has_range_limitations = true; + else + maximum = maximum_default; + const Decimal step = + StepRange::ParseStep(any_step_handling, step_description, + GetElement().FastGetAttribute(stepAttr)); + return StepRange(step_base, minimum, maximum, has_range_limitations, step, + step_description); +} + +void InputType::AddWarningToConsole(const char* message_format, + const String& value) const { + GetElement().GetDocument().AddConsoleMessage(ConsoleMessage::Create( + kRenderingMessageSource, kWarningMessageLevel, + String::Format(message_format, + JSONValue::QuoteString(value).Utf8().data()))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type.h b/chromium/third_party/blink/renderer/core/html/forms/input_type.h new file mode 100644 index 00000000000..5b844c9057d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type.h @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * Copyright (C) 2012 Samsung Electronics. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/frame/web_feature_forward.h" +#include "third_party/blink/renderer/core/html/forms/color_chooser_client.h" +#include "third_party/blink/renderer/core/html/forms/step_range.h" +#include "third_party/blink/renderer/core/html/forms/text_control_element.h" + +namespace blink { + +class ChromeClient; +class DragData; +class ExceptionState; +class FileList; +class FormData; +class InputTypeView; + +// An InputType object represents the type-specific part of an HTMLInputElement. +// Do not expose instances of InputType and classes derived from it to classes +// other than HTMLInputElement. +class CORE_EXPORT InputType : public GarbageCollectedFinalized<InputType> { + public: + static InputType* Create(HTMLInputElement&, const AtomicString&); + static InputType* CreateText(HTMLInputElement&); + static const AtomicString& NormalizeTypeName(const AtomicString&); + virtual ~InputType(); + virtual void Trace(blink::Visitor*); + + virtual InputTypeView* CreateView() = 0; + virtual const AtomicString& FormControlType() const = 0; + + // Type query functions + + // Any time we are using one of these functions it's best to refactor + // to add a virtual function to allow the input type object to do the + // work instead, or at least make a query function that asks a higher + // level question. These functions make the HTMLInputElement class + // inflexible because it's harder to add new input types if there is + // scattered code with special cases for various types. + + virtual bool IsInteractiveContent() const; + virtual bool IsTextButton() const; + virtual bool IsTextField() const; + + // Form value functions + + virtual bool ShouldSaveAndRestoreFormControlState() const; + virtual bool IsFormDataAppendable() const; + virtual void AppendToFormData(FormData&) const; + virtual String ResultForDialogSubmit() const; + + // DOM property functions + + // Returns a string value in ValueMode::kFilename. + virtual String ValueInFilenameValueMode() const; + // Default string to be used for showing button and form submission if |value| + // is missing. + virtual String DefaultLabel() const; + + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value + enum class ValueMode { kValue, kDefault, kDefaultOn, kFilename }; + virtual ValueMode GetValueMode() const = 0; + + virtual double ValueAsDate() const; + virtual void SetValueAsDate(double, ExceptionState&) const; + virtual double ValueAsDouble() const; + virtual void SetValueAsDouble(double, + TextFieldEventBehavior, + ExceptionState&) const; + virtual void SetValueAsDecimal(const Decimal&, + TextFieldEventBehavior, + ExceptionState&) const; + virtual void ReadingChecked() const; + + // Validation functions + + // Returns a validation message as .first, and title attribute value as + // .second if patternMismatch. + std::pair<String, String> ValidationMessage(const InputTypeView&) const; + virtual bool SupportsValidation() const; + virtual bool TypeMismatchFor(const String&) const; + // Type check for the current input value. We do nothing for some types + // though typeMismatchFor() does something for them because of value + // sanitization. + virtual bool TypeMismatch() const; + virtual bool SupportsRequired() const; + virtual bool ValueMissing(const String&) const; + virtual bool PatternMismatch(const String&) const; + virtual bool TooLong(const String&, + TextControlElement::NeedsToCheckDirtyFlag) const; + virtual bool TooShort(const String&, + TextControlElement::NeedsToCheckDirtyFlag) const; + bool RangeUnderflow(const String&) const; + bool RangeOverflow(const String&) const; + bool IsInRange(const String&) const; + bool IsOutOfRange(const String&) const; + void InRangeChanged() const; + virtual Decimal DefaultValueForStepUp() const; + double Minimum() const; + double Maximum() const; + bool StepMismatch(const String&) const; + virtual bool GetAllowedValueStep(Decimal*) const; + virtual StepRange CreateStepRange(AnyStepHandling) const; + virtual void StepUp(double, ExceptionState&); + virtual void StepUpFromLayoutObject(int); + virtual String BadInputText() const; + virtual String RangeOverflowText(const Decimal& maximum) const; + virtual String RangeUnderflowText(const Decimal& minimum) const; + virtual String TypeMismatchText() const; + virtual String ValueMissingText() const; + virtual bool CanSetStringValue() const; + virtual String LocalizeValue(const String&) const; + virtual String VisibleValue() const; + // Returing the null string means "use the default value." + // This function must be called only by HTMLInputElement::sanitizeValue(). + virtual String SanitizeValue(const String&) const; + virtual String SanitizeUserInputValue(const String&) const; + virtual void WarnIfValueIsInvalid(const String&) const; + void WarnIfValueIsInvalidAndElementIsVisible(const String&) const; + + virtual bool IsKeyboardFocusable() const; + virtual bool ShouldShowFocusRingOnMouseFocus() const; + virtual bool CanBeSuccessfulSubmitButton(); + virtual bool MatchesDefaultPseudoClass(); + + // Miscellaneous functions + + virtual bool LayoutObjectIsNeeded(); + virtual void CountUsage(); + virtual void SanitizeValueInResponseToMinOrMaxAttributeChange(); + virtual bool ShouldRespectAlignAttribute(); + virtual FileList* Files(); + virtual void SetFiles(FileList*); + virtual void SetFilesFromPaths(const Vector<String>&); + // Should return true if the given DragData has more than one dropped files. + virtual bool ReceiveDroppedFiles(const DragData*); + virtual String DroppedFileSystemId(); + // Should return true if the corresponding layoutObject for a type can display + // a suggested value. + virtual bool CanSetSuggestedValue(); + virtual bool ShouldSendChangeEventAfterCheckedChanged(); + virtual bool CanSetValue(const String&); + virtual void SetValue(const String&, + bool value_changed, + TextFieldEventBehavior, + TextControlSetValueSelection); + virtual bool ShouldRespectListAttribute(); + virtual bool IsEnumeratable(); + virtual bool IsCheckable(); + virtual bool IsSteppable() const; + virtual bool ShouldRespectHeightAndWidthAttributes(); + virtual int MaxLength() const; + virtual int MinLength() const; + virtual bool SupportsPlaceholder() const; + virtual bool SupportsReadOnly() const; + virtual String DefaultToolTip(const InputTypeView&) const; + virtual Decimal FindClosestTickMarkValue(const Decimal&); + virtual bool HasLegalLinkAttribute(const QualifiedName&) const; + virtual const QualifiedName& SubResourceAttributeName() const; + virtual void CopyNonAttributeProperties(const HTMLInputElement&); + virtual void OnAttachWithLayoutObject(); + virtual void OnDetachWithLayoutObject(); + + // Parses the specified string for the type, and return + // the Decimal value for the parsing result if the parsing + // succeeds; Returns defaultValue otherwise. This function can + // return NaN or Infinity only if defaultValue is NaN or Infinity. + virtual Decimal ParseToNumber(const String&, + const Decimal& default_value) const; + + // Create a string representation of the specified Decimal value for the + // input type. If NaN or Infinity is specified, this returns an empty + // string. This should not be called for types without valueAsNumber. + virtual String Serialize(const Decimal&) const; + + virtual bool ShouldAppearIndeterminate() const; + + virtual bool SupportsInputModeAttribute() const; + + virtual bool SupportsSelectionAPI() const; + + // Gets width and height of the input element if the type of the + // element is image. It returns 0 if the element is not image type. + virtual unsigned Height() const; + virtual unsigned Width() const; + + virtual void DispatchSearchEvent(); + + // For test purpose + virtual ColorChooserClient* GetColorChooserClient(); + + protected: + InputType(HTMLInputElement& element) : element_(element) {} + HTMLInputElement& GetElement() const { return *element_; } + ChromeClient* GetChromeClient() const; + Locale& GetLocale() const; + Decimal ParseToNumberOrNaN(const String&) const; + void CountUsageIfVisible(WebFeature) const; + + // Derive the step base, following the HTML algorithm steps. + Decimal FindStepBase(const Decimal&) const; + + StepRange CreateStepRange(AnyStepHandling, + const Decimal& step_base_default, + const Decimal& minimum_default, + const Decimal& maximum_default, + const StepRange::StepDescription&) const; + void AddWarningToConsole(const char* message_format, + const String& value) const; + + private: + // Helper for stepUp()/stepDown(). Adds step value * count to the current + // value. + void ApplyStep(const Decimal&, + double count, + AnyStepHandling, + TextFieldEventBehavior, + ExceptionState&); + + Member<HTMLInputElement> element_; + + DISALLOW_COPY_AND_ASSIGN(InputType); +}; + +} // namespace blink +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type_names.json5 b/chromium/third_party/blink/renderer/core/html/forms/input_type_names.json5 new file mode 100644 index 00000000000..73487a149d6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_names.json5 @@ -0,0 +1,32 @@ +{ + metadata: { + namespace: "InputType", + export: "CORE_EXPORT", + }, + + data: [ + "button", + "checkbox", + "color", + "date", + "datetime", + "datetime-local", + "email", + "file", + "hidden", + "image", + "month", + "number", + "password", + "radio", + "range", + "reset", + "search", + "submit", + "tel", + "text", + "time", + "url", + "week", + ], +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc new file mode 100644 index 00000000000..273cf3f5172 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.cc @@ -0,0 +1,194 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All + * rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) + * Copyright (C) 2009, 2010, 2011, 2012 Google Inc. All rights reserved. + * 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 "third_party/blink/renderer/core/html/forms/input_type_view.h" + +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" + +namespace blink { + +InputTypeView::~InputTypeView() = default; + +void InputTypeView::Trace(blink::Visitor* visitor) { + visitor->Trace(element_); +} + +bool InputTypeView::SizeShouldIncludeDecoration(int, + int& preferred_size) const { + preferred_size = GetElement().size(); + return false; +} + +void InputTypeView::HandleClickEvent(MouseEvent*) {} + +void InputTypeView::HandleMouseDownEvent(MouseEvent*) {} + +void InputTypeView::HandleKeydownEvent(KeyboardEvent*) {} + +void InputTypeView::HandleKeypressEvent(KeyboardEvent*) {} + +void InputTypeView::HandleKeyupEvent(KeyboardEvent*) {} + +void InputTypeView::HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) {} + +void InputTypeView::HandleDOMActivateEvent(Event*) {} + +void InputTypeView::ForwardEvent(Event*) {} + +void InputTypeView::DispatchSimulatedClickIfActive(KeyboardEvent* event) const { + if (GetElement().IsActive()) + GetElement().DispatchSimulatedClick(event); + event->SetDefaultHandled(); +} + +void InputTypeView::AccessKeyAction(bool) { + GetElement().focus(FocusParams(SelectionBehaviorOnFocus::kReset, + kWebFocusTypeNone, nullptr)); +} + +bool InputTypeView::ShouldSubmitImplicitly(Event* event) { + return event->IsKeyboardEvent() && + event->type() == EventTypeNames::keypress && + ToKeyboardEvent(event)->charCode() == '\r'; +} + +HTMLFormElement* InputTypeView::FormForSubmission() const { + return GetElement().Form(); +} + +LayoutObject* InputTypeView::CreateLayoutObject( + const ComputedStyle& style) const { + return LayoutObject::CreateObject(&GetElement(), style); +} + +scoped_refptr<ComputedStyle> InputTypeView::CustomStyleForLayoutObject( + scoped_refptr<ComputedStyle> original_style) { + return original_style; +} + +TextDirection InputTypeView::ComputedTextDirection() { + return GetElement().EnsureComputedStyle()->Direction(); +} + +void InputTypeView::Blur() { + GetElement().DefaultBlur(); +} + +bool InputTypeView::HasCustomFocusLogic() const { + return true; +} + +void InputTypeView::HandleBlurEvent() {} + +void InputTypeView::HandleFocusInEvent(Element*, WebFocusType) {} + +void InputTypeView::StartResourceLoading() {} + +void InputTypeView::ClosePopupView() {} + +bool InputTypeView::NeedsShadowSubtree() const { + return true; +} + +void InputTypeView::CreateShadowSubtree() {} + +void InputTypeView::DestroyShadowSubtree() { + if (ShadowRoot* root = GetElement().UserAgentShadowRoot()) + root->RemoveChildren(); +} + +void InputTypeView::AltAttributeChanged() {} + +void InputTypeView::SrcAttributeChanged() {} + +void InputTypeView::MinOrMaxAttributeChanged() {} + +void InputTypeView::StepAttributeChanged() {} + +ClickHandlingState* InputTypeView::WillDispatchClick() { + return nullptr; +} + +void InputTypeView::DidDispatchClick(Event*, const ClickHandlingState&) {} + +void InputTypeView::UpdateView() {} + +void InputTypeView::AttributeChanged() {} + +void InputTypeView::MultipleAttributeChanged() {} + +void InputTypeView::DisabledAttributeChanged() {} + +void InputTypeView::ReadonlyAttributeChanged() {} + +void InputTypeView::RequiredAttributeChanged() {} + +void InputTypeView::ValueAttributeChanged() {} + +void InputTypeView::DidSetValue(const String&, bool) {} + +void InputTypeView::SubtreeHasChanged() { + NOTREACHED(); +} + +void InputTypeView::ListAttributeTargetChanged() {} + +void InputTypeView::UpdateClearButtonVisibility() {} + +void InputTypeView::UpdatePlaceholderText() {} + +AXObject* InputTypeView::PopupRootAXObject() { + return nullptr; +} + +FormControlState InputTypeView::SaveFormControlState() const { + String current_value = GetElement().value(); + if (current_value == GetElement().DefaultValue()) + return FormControlState(); + return FormControlState(current_value); +} + +void InputTypeView::RestoreFormControlState(const FormControlState& state) { + GetElement().setValue(state[0]); +} + +bool InputTypeView::HasBadInput() const { + return false; +} + +void ClickHandlingState::Trace(blink::Visitor* visitor) { + visitor->Trace(checked_radio_button); + EventDispatchHandlingState::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h new file mode 100644 index 00000000000..f741beb93ed --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/input_type_view.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * Copyright (C) 2012 Samsung Electronics. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_VIEW_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INPUT_TYPE_VIEW_H_ + +#include "base/macros.h" +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/dom/events/event_dispatcher.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/text/text_direction.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class AXObject; +class BeforeTextInsertedEvent; +class Element; +class Event; +class FormControlState; +class HTMLFormElement; +class HTMLInputElement; +class KeyboardEvent; +class MouseEvent; +class LayoutObject; +class ComputedStyle; + +class ClickHandlingState final : public EventDispatchHandlingState { + public: + void Trace(blink::Visitor*) override; + + bool checked; + bool indeterminate; + Member<HTMLInputElement> checked_radio_button; +}; + +// An InputTypeView object represents the UI-specific part of an +// HTMLInputElement. Do not expose instances of InputTypeView and classes +// derived from it to classes other than HTMLInputElement. +class CORE_EXPORT InputTypeView : public GarbageCollectedMixin { + public: + virtual ~InputTypeView(); + void Trace(blink::Visitor*) override; + + virtual bool SizeShouldIncludeDecoration(int default_size, + int& preferred_size) const; + + // Event handling functions + + virtual void HandleClickEvent(MouseEvent*); + virtual void HandleMouseDownEvent(MouseEvent*); + virtual ClickHandlingState* WillDispatchClick(); + virtual void DidDispatchClick(Event*, const ClickHandlingState&); + virtual void HandleKeydownEvent(KeyboardEvent*); + virtual void HandleKeypressEvent(KeyboardEvent*); + virtual void HandleKeyupEvent(KeyboardEvent*); + virtual void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*); + virtual void ForwardEvent(Event*); + virtual bool ShouldSubmitImplicitly(Event*); + virtual HTMLFormElement* FormForSubmission() const; + virtual bool HasCustomFocusLogic() const; + virtual void HandleFocusInEvent(Element* old_focused_element, WebFocusType); + virtual void HandleBlurEvent(); + virtual void HandleDOMActivateEvent(Event*); + virtual void AccessKeyAction(bool send_mouse_events); + virtual void Blur(); + void DispatchSimulatedClickIfActive(KeyboardEvent*) const; + + virtual void SubtreeHasChanged(); + virtual LayoutObject* CreateLayoutObject(const ComputedStyle&) const; + virtual scoped_refptr<ComputedStyle> CustomStyleForLayoutObject( + scoped_refptr<ComputedStyle>); + virtual TextDirection ComputedTextDirection(); + virtual void StartResourceLoading(); + virtual void ClosePopupView(); + virtual bool NeedsShadowSubtree() const; + virtual void CreateShadowSubtree(); + virtual void DestroyShadowSubtree(); + virtual void MinOrMaxAttributeChanged(); + virtual void StepAttributeChanged(); + virtual void AltAttributeChanged(); + virtual void SrcAttributeChanged(); + virtual void UpdateView(); + virtual void AttributeChanged(); + virtual void MultipleAttributeChanged(); + virtual void DisabledAttributeChanged(); + virtual void ReadonlyAttributeChanged(); + virtual void RequiredAttributeChanged(); + virtual void ValueAttributeChanged(); + virtual void DidSetValue(const String&, bool value_changed); + virtual void ListAttributeTargetChanged(); + virtual void UpdateClearButtonVisibility(); + virtual void UpdatePlaceholderText(); + virtual AXObject* PopupRootAXObject(); + virtual void EnsureFallbackContent() {} + virtual void EnsurePrimaryContent() {} + virtual bool HasFallbackContent() const { return false; } + virtual FormControlState SaveFormControlState() const; + virtual void RestoreFormControlState(const FormControlState&); + + // Validation functions + virtual bool HasBadInput() const; + + protected: + InputTypeView(HTMLInputElement& element) : element_(&element) {} + HTMLInputElement& GetElement() const { return *element_; } + + private: + Member<HTMLInputElement> element_; + + DISALLOW_COPY_AND_ASSIGN(InputTypeView); +}; + +} // namespace blink +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc new file mode 100644 index 00000000000..4e66eb00022 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc @@ -0,0 +1,562 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/internal_popup_menu.h" + +#include "build/build_config.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/public/platform/web_mouse_event.h" +#include "third_party/blink/renderer/core/css/css_font_selector.h" +#include "third_party/blink/renderer/core/css/style_engine.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/exported/web_view_impl.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/html/html_hr_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page_popup.h" +#include "third_party/blink/renderer/platform/fonts/font_selector.h" +#include "third_party/blink/renderer/platform/fonts/font_selector_client.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +namespace { + +// TODO crbug.com/516675 Add stretch to serialization + +const char* FontStyleToString(FontSelectionValue slope) { + if (slope == ItalicSlopeValue()) + return "italic"; + return "normal"; +} + +const char* TextTransformToString(ETextTransform transform) { + switch (transform) { + case ETextTransform::kCapitalize: + return "capitalize"; + case ETextTransform::kUppercase: + return "uppercase"; + case ETextTransform::kLowercase: + return "lowercase"; + case ETextTransform::kNone: + return "none"; + } + NOTREACHED(); + return ""; +} + +} // anonymous namespace + +class PopupMenuCSSFontSelector : public CSSFontSelector, + private FontSelectorClient { + USING_GARBAGE_COLLECTED_MIXIN(PopupMenuCSSFontSelector); + + public: + static PopupMenuCSSFontSelector* Create( + Document* document, + CSSFontSelector* owner_font_selector) { + return new PopupMenuCSSFontSelector(document, owner_font_selector); + } + + ~PopupMenuCSSFontSelector() override; + + // We don't override willUseFontData() for now because the old PopupListBox + // only worked with fonts loaded when opening the popup. + scoped_refptr<FontData> GetFontData(const FontDescription&, + const AtomicString&) override; + + void Trace(blink::Visitor*) override; + + private: + PopupMenuCSSFontSelector(Document*, CSSFontSelector*); + + void FontsNeedUpdate(FontSelector*) override; + + Member<CSSFontSelector> owner_font_selector_; +}; + +PopupMenuCSSFontSelector::PopupMenuCSSFontSelector( + Document* document, + CSSFontSelector* owner_font_selector) + : CSSFontSelector(document), owner_font_selector_(owner_font_selector) { + owner_font_selector_->RegisterForInvalidationCallbacks(this); +} + +PopupMenuCSSFontSelector::~PopupMenuCSSFontSelector() = default; + +scoped_refptr<FontData> PopupMenuCSSFontSelector::GetFontData( + const FontDescription& description, + const AtomicString& name) { + return owner_font_selector_->GetFontData(description, name); +} + +void PopupMenuCSSFontSelector::FontsNeedUpdate(FontSelector* font_selector) { + DispatchInvalidationCallbacks(); +} + +void PopupMenuCSSFontSelector::Trace(blink::Visitor* visitor) { + visitor->Trace(owner_font_selector_); + CSSFontSelector::Trace(visitor); + FontSelectorClient::Trace(visitor); +} + +// ---------------------------------------------------------------- + +class InternalPopupMenu::ItemIterationContext { + STACK_ALLOCATED(); + + public: + ItemIterationContext(const ComputedStyle& style, SharedBuffer* buffer) + : base_style_(style), + background_color_( + style.VisitedDependentColor(GetCSSPropertyBackgroundColor())), + list_index_(0), + is_in_group_(false), + buffer_(buffer) { + DCHECK(buffer_); +#if defined(OS_LINUX) + // On other platforms, the <option> background color is the same as the + // <select> background color. On Linux, that makes the <option> + // background color very dark, so by default, try to use a lighter + // background color for <option>s. + if (LayoutTheme::GetTheme().SystemColor(CSSValueButtonface) == + background_color_) + background_color_ = LayoutTheme::GetTheme().SystemColor(CSSValueMenu); +#endif + } + + void SerializeBaseStyle() { + DCHECK(!is_in_group_); + PagePopupClient::AddString("baseStyle: {", buffer_); + AddProperty("backgroundColor", background_color_.Serialized(), buffer_); + AddProperty( + "color", + BaseStyle().VisitedDependentColor(GetCSSPropertyColor()).Serialized(), + buffer_); + AddProperty("textTransform", + String(TextTransformToString(BaseStyle().TextTransform())), + buffer_); + AddProperty("fontSize", BaseFont().ComputedPixelSize(), buffer_); + AddProperty("fontStyle", String(FontStyleToString(BaseFont().Style())), + buffer_); + AddProperty("fontVariant", + BaseFont().VariantCaps() == FontDescription::kSmallCaps + ? String("small-caps") + : String(), + buffer_); + + PagePopupClient::AddString("fontFamily: [", buffer_); + for (const FontFamily* f = &BaseFont().Family(); f; f = f->Next()) { + AddJavaScriptString(f->Family().GetString(), buffer_); + if (f->Next()) + PagePopupClient::AddString(",", buffer_); + } + PagePopupClient::AddString("]", buffer_); + PagePopupClient::AddString("},\n", buffer_); + } + + Color BackgroundColor() const { + return is_in_group_ ? group_style_->VisitedDependentColor( + GetCSSPropertyBackgroundColor()) + : background_color_; + } + // Do not use baseStyle() for background-color, use backgroundColor() + // instead. + const ComputedStyle& BaseStyle() { + return is_in_group_ ? *group_style_ : base_style_; + } + const FontDescription& BaseFont() { + return is_in_group_ ? group_style_->GetFontDescription() + : base_style_.GetFontDescription(); + } + void StartGroupChildren(const ComputedStyle& group_style) { + DCHECK(!is_in_group_); + PagePopupClient::AddString("children: [", buffer_); + is_in_group_ = true; + group_style_ = &group_style; + } + void FinishGroupIfNecessary() { + if (!is_in_group_) + return; + PagePopupClient::AddString("],},\n", buffer_); + is_in_group_ = false; + group_style_ = nullptr; + } + + const ComputedStyle& base_style_; + Color background_color_; + const ComputedStyle* group_style_; + + unsigned list_index_; + bool is_in_group_; + SharedBuffer* buffer_; +}; + +// ---------------------------------------------------------------- + +InternalPopupMenu* InternalPopupMenu::Create(ChromeClient* chrome_client, + HTMLSelectElement& owner_element) { + return new InternalPopupMenu(chrome_client, owner_element); +} + +InternalPopupMenu::InternalPopupMenu(ChromeClient* chrome_client, + HTMLSelectElement& owner_element) + : chrome_client_(chrome_client), + owner_element_(owner_element), + popup_(nullptr), + needs_update_(false) {} + +InternalPopupMenu::~InternalPopupMenu() { + DCHECK(!popup_); +} + +void InternalPopupMenu::Trace(blink::Visitor* visitor) { + visitor->Trace(chrome_client_); + visitor->Trace(owner_element_); + PopupMenu::Trace(visitor); +} + +void InternalPopupMenu::WriteDocument(SharedBuffer* data) { + HTMLSelectElement& owner_element = *owner_element_; + IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen( + owner_element.VisibleBoundsInVisualViewport(), + owner_element.GetDocument().View()); + + float scale_factor = chrome_client_->WindowToViewportScalar(1.f); + PagePopupClient::AddString( + "<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data); + data->Append(Platform::Current()->GetDataResource("pickerCommon.css")); + data->Append(Platform::Current()->GetDataResource("listPicker.css")); + if (!RuntimeEnabledFeatures::ForceTallerSelectPopupEnabled()) + PagePopupClient::AddString("@media (any-pointer:coarse) {", data); + int padding = static_cast<int>(roundf(4 * scale_factor)); + int min_height = static_cast<int>(roundf(24 * scale_factor)); + PagePopupClient::AddString(String::Format("option, optgroup {" + "padding-top: %dpx;" + "}\n" + "option {" + "padding-bottom: %dpx;" + "min-height: %dpx;" + "display: flex;" + "align-items: center;" + "}", + padding, padding, min_height), + data); + if (!RuntimeEnabledFeatures::ForceTallerSelectPopupEnabled()) { + // Closes @media. + PagePopupClient::AddString("}", data); + } + + PagePopupClient::AddString( + "</style></head><body><div id=main>Loading...</div><script>\n" + "window.dialogArguments = {\n", + data); + AddProperty("selectedIndex", owner_element.SelectedListIndex(), data); + const ComputedStyle* owner_style = owner_element.GetComputedStyle(); + ItemIterationContext context(*owner_style, data); + context.SerializeBaseStyle(); + PagePopupClient::AddString("children: [\n", data); + const HeapVector<Member<HTMLElement>>& items = owner_element.GetListItems(); + for (; context.list_index_ < items.size(); ++context.list_index_) { + Element& child = *items[context.list_index_]; + if (!IsHTMLOptGroupElement(child.parentNode())) + context.FinishGroupIfNecessary(); + if (auto* option = ToHTMLOptionElementOrNull(child)) + AddOption(context, *option); + else if (auto* optgroup = ToHTMLOptGroupElementOrNull(child)) + AddOptGroup(context, *optgroup); + else if (auto* hr = ToHTMLHRElementOrNull(child)) + AddSeparator(context, *hr); + } + context.FinishGroupIfNecessary(); + PagePopupClient::AddString("],\n", data); + + AddProperty("anchorRectInScreen", anchor_rect_in_screen, data); + AddProperty("zoomFactor", 1, data); + AddProperty("scaleFactor", scale_factor, data); + bool is_rtl = !owner_style->IsLeftToRightDirection(); + AddProperty("isRTL", is_rtl, data); + AddProperty("paddingStart", + is_rtl ? owner_element.ClientPaddingRight().ToDouble() + : owner_element.ClientPaddingLeft().ToDouble(), + data); + PagePopupClient::AddString("};\n", data); + data->Append(Platform::Current()->GetDataResource("pickerCommon.js")); + data->Append(Platform::Current()->GetDataResource("listPicker.js")); + PagePopupClient::AddString("</script></body>\n", data); +} + +void InternalPopupMenu::AddElementStyle(ItemIterationContext& context, + HTMLElement& element) { + const ComputedStyle* style = owner_element_->ItemComputedStyle(element); + DCHECK(style); + SharedBuffer* data = context.buffer_; + // TODO(tkent): We generate unnecessary "style: {\n},\n" even if no + // additional style. + PagePopupClient::AddString("style: {\n", data); + if (style->Visibility() == EVisibility::kHidden) + AddProperty("visibility", String("hidden"), data); + if (style->Display() == EDisplay::kNone) + AddProperty("display", String("none"), data); + const ComputedStyle& base_style = context.BaseStyle(); + if (base_style.Direction() != style->Direction()) { + AddProperty( + "direction", + String(style->Direction() == TextDirection::kRtl ? "rtl" : "ltr"), + data); + } + if (IsOverride(style->GetUnicodeBidi())) + AddProperty("unicodeBidi", String("bidi-override"), data); + Color foreground_color = style->VisitedDependentColor(GetCSSPropertyColor()); + if (base_style.VisitedDependentColor(GetCSSPropertyColor()) != + foreground_color) + AddProperty("color", foreground_color.Serialized(), data); + Color background_color = + style->VisitedDependentColor(GetCSSPropertyBackgroundColor()); + if (context.BackgroundColor() != background_color && + background_color != Color::kTransparent) + AddProperty("backgroundColor", background_color.Serialized(), data); + const FontDescription& base_font = context.BaseFont(); + const FontDescription& font_description = + style->GetFont().GetFontDescription(); + if (base_font.ComputedPixelSize() != font_description.ComputedPixelSize()) { + // We don't use FontDescription::specifiedSize() because this element + // might have its own zoom level. + AddProperty("fontSize", font_description.ComputedPixelSize(), data); + } + // Our UA stylesheet has font-weight:normal for OPTION. + if (NormalWeightValue() != font_description.Weight()) { + AddProperty("fontWeight", String::Number(font_description.Weight()), data); + } + if (base_font.Family() != font_description.Family()) { + PagePopupClient::AddString("fontFamily: [\n", data); + for (const FontFamily* f = &font_description.Family(); f; f = f->Next()) { + AddJavaScriptString(f->Family().GetString(), data); + if (f->Next()) + PagePopupClient::AddString(",\n", data); + } + PagePopupClient::AddString("],\n", data); + } + if (base_font.Style() != font_description.Style()) { + AddProperty("fontStyle", + String(FontStyleToString(font_description.Style())), data); + } + + if (base_font.VariantCaps() != font_description.VariantCaps() && + font_description.VariantCaps() == FontDescription::kSmallCaps) + AddProperty("fontVariant", String("small-caps"), data); + + if (base_style.TextTransform() != style->TextTransform()) { + AddProperty("textTransform", + String(TextTransformToString(style->TextTransform())), data); + } + + PagePopupClient::AddString("},\n", data); +} + +void InternalPopupMenu::AddOption(ItemIterationContext& context, + HTMLOptionElement& element) { + SharedBuffer* data = context.buffer_; + PagePopupClient::AddString("{", data); + AddProperty("label", element.DisplayLabel(), data); + AddProperty("value", context.list_index_, data); + if (!element.title().IsEmpty()) + AddProperty("title", element.title(), data); + const AtomicString& aria_label = + element.FastGetAttribute(HTMLNames::aria_labelAttr); + if (!aria_label.IsEmpty()) + AddProperty("ariaLabel", aria_label, data); + if (element.IsDisabledFormControl()) + AddProperty("disabled", true, data); + AddElementStyle(context, element); + PagePopupClient::AddString("},", data); +} + +void InternalPopupMenu::AddOptGroup(ItemIterationContext& context, + HTMLOptGroupElement& element) { + SharedBuffer* data = context.buffer_; + PagePopupClient::AddString("{\n", data); + PagePopupClient::AddString("type: \"optgroup\",\n", data); + AddProperty("label", element.GroupLabelText(), data); + AddProperty("title", element.title(), data); + AddProperty("ariaLabel", element.FastGetAttribute(HTMLNames::aria_labelAttr), + data); + AddProperty("disabled", element.IsDisabledFormControl(), data); + AddElementStyle(context, element); + context.StartGroupChildren(*owner_element_->ItemComputedStyle(element)); + // We should call ItemIterationContext::finishGroupIfNecessary() later. +} + +void InternalPopupMenu::AddSeparator(ItemIterationContext& context, + HTMLHRElement& element) { + SharedBuffer* data = context.buffer_; + PagePopupClient::AddString("{\n", data); + PagePopupClient::AddString("type: \"separator\",\n", data); + AddProperty("title", element.title(), data); + AddProperty("ariaLabel", element.FastGetAttribute(HTMLNames::aria_labelAttr), + data); + AddProperty("disabled", element.IsDisabledFormControl(), data); + AddElementStyle(context, element); + PagePopupClient::AddString("},\n", data); +} + +void InternalPopupMenu::SelectFontsFromOwnerDocument(Document& document) { + Document& owner_document = OwnerElement().GetDocument(); + document.GetStyleEngine().SetFontSelector(PopupMenuCSSFontSelector::Create( + &document, owner_document.GetStyleEngine().GetFontSelector())); +} + +void InternalPopupMenu::SetValueAndClosePopup(int num_value, + const String& string_value) { + DCHECK(popup_); + DCHECK(owner_element_); + if (!string_value.IsEmpty()) { + bool success; + int list_index = string_value.ToInt(&success); + DCHECK(success); + + EventQueueScope scope; + owner_element_->SelectOptionByPopup(list_index); + if (popup_) + chrome_client_->ClosePagePopup(popup_); + // 'change' event is dispatched here. For compatbility with + // Angular 1.2, we need to dispatch a change event before + // mouseup/click events. + } else { + if (popup_) + chrome_client_->ClosePagePopup(popup_); + } + // We dispatch events on the owner element to match the legacy behavior. + // Other browsers dispatch click events before and after showing the popup. + if (owner_element_) { + // TODO(dtapuska): Why is this event positionless? + WebMouseEvent event; + event.SetFrameScale(1); + Element* owner = &OwnerElement(); + owner->DispatchMouseEvent(event, EventTypeNames::mouseup); + owner->DispatchMouseEvent(event, EventTypeNames::click); + } +} + +void InternalPopupMenu::SetValue(const String& value) { + DCHECK(owner_element_); + bool success; + int list_index = value.ToInt(&success); + DCHECK(success); + owner_element_->ProvisionalSelectionChanged(list_index); +} + +void InternalPopupMenu::DidClosePopup() { + // Clearing m_popup first to prevent from trying to close the popup again. + popup_ = nullptr; + if (owner_element_) + owner_element_->PopupDidHide(); +} + +Element& InternalPopupMenu::OwnerElement() { + return *owner_element_; +} + +Locale& InternalPopupMenu::GetLocale() { + return Locale::DefaultLocale(); +} + +void InternalPopupMenu::ClosePopup() { + if (popup_) + chrome_client_->ClosePagePopup(popup_); + if (owner_element_) + owner_element_->PopupDidCancel(); +} + +void InternalPopupMenu::Dispose() { + if (popup_) + chrome_client_->ClosePagePopup(popup_); +} + +void InternalPopupMenu::Show() { + DCHECK(!popup_); + popup_ = chrome_client_->OpenPagePopup(this); +} + +void InternalPopupMenu::Hide() { + ClosePopup(); +} + +void InternalPopupMenu::UpdateFromElement(UpdateReason) { + if (needs_update_) + return; + needs_update_ = true; + OwnerElement() + .GetDocument() + .GetTaskRunner(TaskType::kUserInteraction) + ->PostTask(FROM_HERE, + WTF::Bind(&InternalPopupMenu::Update, WrapPersistent(this))); +} + +void InternalPopupMenu::Update() { + if (!popup_ || !owner_element_) + return; + OwnerElement().GetDocument().UpdateStyleAndLayoutTree(); + // disconnectClient() might have been called. + if (!owner_element_) + return; + needs_update_ = false; + + if (!OwnerElement() + .GetDocument() + .GetFrame() + ->View() + ->VisibleContentRect() + .Intersects(OwnerElement().PixelSnappedBoundingBox())) { + Hide(); + return; + } + + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + PagePopupClient::AddString("window.updateData = {\n", data.get()); + PagePopupClient::AddString("type: \"update\",\n", data.get()); + ItemIterationContext context(*owner_element_->GetComputedStyle(), data.get()); + context.SerializeBaseStyle(); + PagePopupClient::AddString("children: [", data.get()); + const HeapVector<Member<HTMLElement>>& items = owner_element_->GetListItems(); + for (; context.list_index_ < items.size(); ++context.list_index_) { + Element& child = *items[context.list_index_]; + if (!IsHTMLOptGroupElement(child.parentNode())) + context.FinishGroupIfNecessary(); + if (auto* option = ToHTMLOptionElementOrNull(child)) + AddOption(context, *option); + else if (auto* optgroup = ToHTMLOptGroupElementOrNull(child)) + AddOptGroup(context, *optgroup); + else if (auto* hr = ToHTMLHRElementOrNull(child)) + AddSeparator(context, *hr); + } + context.FinishGroupIfNecessary(); + PagePopupClient::AddString("],\n", data.get()); + IntRect anchor_rect_in_screen = chrome_client_->ViewportToScreen( + owner_element_->VisibleBoundsInVisualViewport(), + OwnerElement().GetDocument().View()); + AddProperty("anchorRectInScreen", anchor_rect_in_screen, data.get()); + PagePopupClient::AddString("}\n", data.get()); + popup_->PostMessageToPopup(String::FromUTF8(data->Data(), data->size())); +} + +void InternalPopupMenu::DisconnectClient() { + owner_element_ = nullptr; + // Cannot be done during finalization, so instead done when the + // layout object is destroyed and disconnected. + Dispose(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h new file mode 100644 index 00000000000..e3e7a69d08e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/internal_popup_menu.h @@ -0,0 +1,69 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INTERNAL_POPUP_MENU_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INTERNAL_POPUP_MENU_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/popup_menu.h" +#include "third_party/blink/renderer/core/page/page_popup_client.h" + +namespace blink { + +class ChromeClient; +class PagePopup; +class HTMLElement; +class HTMLHRElement; +class HTMLOptGroupElement; +class HTMLOptionElement; +class HTMLSelectElement; + +// InternalPopupMenu is a PopupMenu implementation for platforms other than +// macOS and Android. The UI is built with an HTML page inside a PagePopup. +class CORE_EXPORT InternalPopupMenu final : public PopupMenu, + public PagePopupClient { + public: + static InternalPopupMenu* Create(ChromeClient*, HTMLSelectElement&); + ~InternalPopupMenu() override; + void Trace(blink::Visitor*) override; + + void Update(); + + void Dispose(); + + private: + InternalPopupMenu(ChromeClient*, HTMLSelectElement&); + + class ItemIterationContext; + void AddOption(ItemIterationContext&, HTMLOptionElement&); + void AddOptGroup(ItemIterationContext&, HTMLOptGroupElement&); + void AddSeparator(ItemIterationContext&, HTMLHRElement&); + void AddElementStyle(ItemIterationContext&, HTMLElement&); + + // PopupMenu functions: + void Show() override; + void Hide() override; + void DisconnectClient() override; + void UpdateFromElement(UpdateReason) override; + + // PagePopupClient functions: + void WriteDocument(SharedBuffer*) override; + void SelectFontsFromOwnerDocument(Document&) override; + void SetValueAndClosePopup(int, const String&) override; + void SetValue(const String&) override; + void ClosePopup() override; + Element& OwnerElement() override; + float ZoomFactor() override { return 1.0; } + Locale& GetLocale() override; + void DidClosePopup() override; + + Member<ChromeClient> chrome_client_; + Member<HTMLSelectElement> owner_element_; + PagePopup* popup_; + bool needs_update_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_INTERNAL_POPUP_MENU_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc new file mode 100644 index 00000000000..848859cea96 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.cc @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010, 2012 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h" + +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" + +namespace blink { + +using namespace HTMLNames; + +void KeyboardClickableInputTypeView::HandleKeydownEvent(KeyboardEvent* event) { + const String& key = event->key(); + if (key == " ") { + GetElement().SetActive(true); + // No setDefaultHandled(), because IE dispatches a keypress in this case + // and the caller will only dispatch a keypress if we don't call + // setDefaultHandled(). + } +} + +void KeyboardClickableInputTypeView::HandleKeypressEvent(KeyboardEvent* event) { + const String& key = event->key(); + if (key == "Enter") { + GetElement().DispatchSimulatedClick(event); + event->SetDefaultHandled(); + return; + } + if (key == " ") { + // Prevent scrolling down the page. + event->SetDefaultHandled(); + } +} + +void KeyboardClickableInputTypeView::HandleKeyupEvent(KeyboardEvent* event) { + const String& key = event->key(); + if (key != " ") + return; + // Simulate mouse click for spacebar for button types. + DispatchSimulatedClickIfActive(event); +} + +// FIXME: Could share this with BaseCheckableInputType and RangeInputType if we +// had a common base class. +void KeyboardClickableInputTypeView::AccessKeyAction(bool send_mouse_events) { + InputTypeView::AccessKeyAction(send_mouse_events); + GetElement().DispatchSimulatedClick( + nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h new file mode 100644 index 00000000000..1dc03b1e7ac --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/keyboard_clickable_input_type_view.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_KEYBOARD_CLICKABLE_INPUT_TYPE_VIEW_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_KEYBOARD_CLICKABLE_INPUT_TYPE_VIEW_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/input_type_view.h" + +namespace blink { + +// An InputTypeView class that dispatches a simulated click on space/return key. +class CORE_EXPORT KeyboardClickableInputTypeView : public InputTypeView { + protected: + KeyboardClickableInputTypeView(HTMLInputElement& element) + : InputTypeView(element) {} + + protected: + void HandleKeydownEvent(KeyboardEvent*) override; + void HandleKeypressEvent(KeyboardEvent*) override; + void HandleKeyupEvent(KeyboardEvent*) override; + void AccessKeyAction(bool send_mouse_events) override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_KEYBOARD_CLICKABLE_INPUT_TYPE_VIEW_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc new file mode 100644 index 00000000000..a8641324673 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/labelable_element.h" + +#include "third_party/blink/renderer/core/dom/node_lists_node_data.h" +#include "third_party/blink/renderer/core/dom/node_rare_data.h" +#include "third_party/blink/renderer/core/html/forms/labels_node_list.h" + +namespace blink { + +LabelableElement::LabelableElement(const QualifiedName& tag_name, + Document& document) + : HTMLElement(tag_name, document) {} + +LabelableElement::~LabelableElement() = default; + +LabelsNodeList* LabelableElement::labels() { + if (!SupportLabels()) + return nullptr; + + return EnsureCachedCollection<LabelsNodeList>(kLabelsNodeListType); +} + +void LabelableElement::Trace(blink::Visitor* visitor) { + HTMLElement::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/labelable_element.h b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.h new file mode 100644 index 00000000000..71fb17d6fa3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/labelable_element.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELABLE_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELABLE_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/html_element.h" + +namespace blink { + +class LabelsNodeList; + +// LabelableElement represents "labelable element" defined in the HTML +// specification, and provides the implementation of the "labels" attribute. +class CORE_EXPORT LabelableElement : public HTMLElement { + public: + ~LabelableElement() override; + LabelsNodeList* labels(); + virtual bool SupportLabels() const { return false; } + + void Trace(blink::Visitor*) override; + + protected: + LabelableElement(const QualifiedName& tag_name, Document&); + + private: + bool IsLabelable() const final { return true; } +}; + +inline bool IsLabelableElement(const HTMLElement& element) { + return element.IsLabelable(); +} + +DEFINE_HTMLELEMENT_TYPE_CASTS_WITH_FUNCTION(LabelableElement); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc new file mode 100644 index 00000000000..e2ff16dda4a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.cc @@ -0,0 +1,49 @@ +/** + * 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, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2010 Nokia Inc. 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 "third_party/blink/renderer/core/html/forms/labels_node_list.h" + +#include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/dom/node_rare_data.h" +#include "third_party/blink/renderer/core/html/forms/html_label_element.h" +#include "third_party/blink/renderer/core/html/forms/labelable_element.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +using namespace HTMLNames; + +LabelsNodeList::LabelsNodeList(ContainerNode& owner_node) + : LiveNodeList(owner_node, + kLabelsNodeListType, + kInvalidateForFormControls, + NodeListSearchRoot::kTreeScope) {} + +LabelsNodeList::~LabelsNodeList() = default; + +bool LabelsNodeList::ElementMatches(const Element& element) const { + return IsHTMLLabelElement(element) && + ToHTMLLabelElement(element).control() == ownerNode(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h new file mode 100644 index 00000000000..fa5b33993ce --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/labels_node_list.h @@ -0,0 +1,51 @@ +/* + * 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, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2010 Nokia Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELS_NODE_LIST_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELS_NODE_LIST_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/dom/live_node_list.h" + +namespace blink { + +class LabelsNodeList final : public LiveNodeList { + public: + static LabelsNodeList* Create(ContainerNode& owner_node, + CollectionType type) { + DCHECK_EQ(type, kLabelsNodeListType); + return new LabelsNodeList(owner_node); + } + + ~LabelsNodeList() override; + + protected: + explicit LabelsNodeList(ContainerNode&); + + bool ElementMatches(const Element&) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LABELS_NODE_LIST_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc b/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc new file mode 100644 index 00000000000..dee6e10f57b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/listed_element.cc @@ -0,0 +1,326 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/listed_element.h" + +#include "third_party/blink/renderer/core/dom/id_target_observer.h" +#include "third_party/blink/renderer/core/dom/node_traversal.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/validity_state.h" +#include "third_party/blink/renderer/core/html/html_object_element.h" +#include "third_party/blink/renderer/core/html_names.h" + +namespace blink { + +using namespace HTMLNames; + +class FormAttributeTargetObserver : public IdTargetObserver { + public: + static FormAttributeTargetObserver* Create(const AtomicString& id, + ListedElement*); + void Trace(blink::Visitor*) override; + void IdTargetChanged() override; + + private: + FormAttributeTargetObserver(const AtomicString& id, ListedElement*); + + Member<ListedElement> element_; +}; + +ListedElement::ListedElement() : form_was_set_by_parser_(false) {} + +ListedElement::~ListedElement() { + // We can't call setForm here because it contains virtual calls. +} + +void ListedElement::Trace(blink::Visitor* visitor) { + visitor->Trace(form_attribute_target_observer_); + visitor->Trace(form_); + visitor->Trace(validity_state_); +} + +ValidityState* ListedElement::validity() { + if (!validity_state_) + validity_state_ = ValidityState::Create(this); + + return validity_state_.Get(); +} + +void ListedElement::DidMoveToNewDocument(Document& old_document) { + HTMLElement* element = ToHTMLElement(this); + if (element->FastHasAttribute(formAttr)) + SetFormAttributeTargetObserver(nullptr); +} + +void ListedElement::InsertedInto(ContainerNode* insertion_point) { + if (!form_was_set_by_parser_ || !form_ || + NodeTraversal::HighestAncestorOrSelf(*insertion_point) != + NodeTraversal::HighestAncestorOrSelf(*form_.Get())) + ResetFormOwner(); + + if (!insertion_point->isConnected()) + return; + + HTMLElement* element = ToHTMLElement(this); + if (element->FastHasAttribute(formAttr)) + ResetFormAttributeTargetObserver(); +} + +void ListedElement::RemovedFrom(ContainerNode* insertion_point) { + HTMLElement* element = ToHTMLElement(this); + if (insertion_point->isConnected() && element->FastHasAttribute(formAttr)) { + SetFormAttributeTargetObserver(nullptr); + ResetFormOwner(); + return; + } + // If the form and element are both in the same tree, preserve the connection + // to the form. Otherwise, null out our form and remove ourselves from the + // form's list of elements. + if (form_ && NodeTraversal::HighestAncestorOrSelf(*element) != + NodeTraversal::HighestAncestorOrSelf(*form_.Get())) + ResetFormOwner(); +} + +HTMLFormElement* ListedElement::FindAssociatedForm( + const HTMLElement* element, + const AtomicString& form_id, + HTMLFormElement* form_ancestor) { + // 3. If the element is reassociateable, has a form content attribute, and + // is itself in a Document, then run these substeps: + if (!form_id.IsNull() && element->isConnected()) { + // 3.1. If the first element in the Document to have an ID that is + // case-sensitively equal to the element's form content attribute's + // value is a form element, then associate the form-associated element + // with that form element. + // 3.2. Abort the "reset the form owner" steps. + Element* new_form_candidate = + element->GetTreeScope().getElementById(form_id); + return ToHTMLFormElementOrNull(new_form_candidate); + } + // 4. Otherwise, if the form-associated element in question has an ancestor + // form element, then associate the form-associated element with the nearest + // such ancestor form element. + return form_ancestor; +} + +void ListedElement::FormRemovedFromTree(const Node& form_root) { + DCHECK(form_); + if (NodeTraversal::HighestAncestorOrSelf(ToHTMLElement(*this)) == form_root) + return; + ResetFormOwner(); +} + +void ListedElement::AssociateByParser(HTMLFormElement* form) { + if (form && form->isConnected()) { + form_was_set_by_parser_ = true; + SetForm(form); + form->DidAssociateByParser(); + } +} + +void ListedElement::SetForm(HTMLFormElement* new_form) { + if (form_.Get() == new_form) + return; + WillChangeForm(); + if (form_) + form_->Disassociate(*this); + if (new_form) { + form_ = new_form; + form_->Associate(*this); + } else { + form_ = nullptr; + } + DidChangeForm(); +} + +void ListedElement::WillChangeForm() {} + +void ListedElement::DidChangeForm() { + if (!form_was_set_by_parser_ && form_ && form_->isConnected()) { + HTMLElement* element = ToHTMLElement(this); + element->GetDocument().DidAssociateFormControl(element); + } +} + +void ListedElement::ResetFormOwner() { + form_was_set_by_parser_ = false; + HTMLElement* element = ToHTMLElement(this); + const AtomicString& form_id(element->FastGetAttribute(formAttr)); + HTMLFormElement* nearest_form = element->FindFormAncestor(); + // 1. If the element's form owner is not null, and either the element is not + // reassociateable or its form content attribute is not present, and the + // element's form owner is its nearest form element ancestor after the + // change to the ancestor chain, then do nothing, and abort these steps. + if (form_ && form_id.IsNull() && form_.Get() == nearest_form) + return; + + SetForm(FindAssociatedForm(element, form_id, nearest_form)); +} + +void ListedElement::FormAttributeChanged() { + ResetFormOwner(); + ResetFormAttributeTargetObserver(); +} + +bool ListedElement::CustomError() const { + const HTMLElement* element = ToHTMLElement(this); + return element->willValidate() && !custom_validation_message_.IsEmpty(); +} + +bool ListedElement::HasBadInput() const { + return false; +} + +bool ListedElement::PatternMismatch() const { + return false; +} + +bool ListedElement::RangeOverflow() const { + return false; +} + +bool ListedElement::RangeUnderflow() const { + return false; +} + +bool ListedElement::StepMismatch() const { + return false; +} + +bool ListedElement::TooLong() const { + return false; +} + +bool ListedElement::TooShort() const { + return false; +} + +bool ListedElement::TypeMismatch() const { + return false; +} + +bool ListedElement::Valid() const { + bool some_error = TypeMismatch() || StepMismatch() || RangeUnderflow() || + RangeOverflow() || TooLong() || TooShort() || + PatternMismatch() || ValueMissing() || HasBadInput() || + CustomError(); + return !some_error; +} + +bool ListedElement::ValueMissing() const { + return false; +} + +String ListedElement::CustomValidationMessage() const { + return custom_validation_message_; +} + +String ListedElement::validationMessage() const { + return CustomError() ? custom_validation_message_ : String(); +} + +String ListedElement::ValidationSubMessage() const { + return String(); +} + +void ListedElement::setCustomValidity(const String& error) { + custom_validation_message_ = error; +} + +void ListedElement::SetFormAttributeTargetObserver( + FormAttributeTargetObserver* new_observer) { + if (form_attribute_target_observer_) + form_attribute_target_observer_->Unregister(); + form_attribute_target_observer_ = new_observer; +} + +void ListedElement::ResetFormAttributeTargetObserver() { + HTMLElement* element = ToHTMLElement(this); + const AtomicString& form_id(element->FastGetAttribute(formAttr)); + if (!form_id.IsNull() && element->isConnected()) { + SetFormAttributeTargetObserver( + FormAttributeTargetObserver::Create(form_id, this)); + } else { + SetFormAttributeTargetObserver(nullptr); + } +} + +void ListedElement::FormAttributeTargetChanged() { + ResetFormOwner(); +} + +const AtomicString& ListedElement::GetName() const { + const AtomicString& name = ToHTMLElement(this)->GetNameAttribute(); + return name.IsNull() ? g_empty_atom : name; +} + +bool ListedElement::IsFormControlElementWithState() const { + return false; +} + +const HTMLElement& ToHTMLElement(const ListedElement& listed_element) { + if (listed_element.IsFormControlElement()) + return ToHTMLFormControlElement(listed_element); + return ToHTMLObjectElementFromListedElement(listed_element); +} + +const HTMLElement* ToHTMLElement(const ListedElement* listed_element) { + DCHECK(listed_element); + return &ToHTMLElement(*listed_element); +} + +HTMLElement* ToHTMLElement(ListedElement* listed_element) { + return const_cast<HTMLElement*>( + ToHTMLElement(static_cast<const ListedElement*>(listed_element))); +} + +HTMLElement& ToHTMLElement(ListedElement& listed_element) { + return const_cast<HTMLElement&>( + ToHTMLElement(static_cast<const ListedElement&>(listed_element))); +} + +FormAttributeTargetObserver* FormAttributeTargetObserver::Create( + const AtomicString& id, + ListedElement* element) { + return new FormAttributeTargetObserver(id, element); +} + +FormAttributeTargetObserver::FormAttributeTargetObserver(const AtomicString& id, + ListedElement* element) + : IdTargetObserver( + ToHTMLElement(element)->GetTreeScope().GetIdTargetObserverRegistry(), + id), + element_(element) {} + +void FormAttributeTargetObserver::Trace(blink::Visitor* visitor) { + visitor->Trace(element_); + IdTargetObserver::Trace(visitor); +} + +void FormAttributeTargetObserver::IdTargetChanged() { + element_->FormAttributeTargetChanged(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/listed_element.h b/chromium/third_party/blink/renderer/core/html/forms/listed_element.h new file mode 100644 index 00000000000..891cb564d2c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/listed_element.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LISTED_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LISTED_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +class ContainerNode; +class Document; +class FormAttributeTargetObserver; +class FormData; +class HTMLElement; +class HTMLFormElement; +class Node; +class ValidityState; + +// https://html.spec.whatwg.org/multipage/forms.html#category-listed +class CORE_EXPORT ListedElement : public GarbageCollectedMixin { + public: + virtual ~ListedElement(); + + static HTMLFormElement* FindAssociatedForm(const HTMLElement*, + const AtomicString& form_id, + HTMLFormElement* form_ancestor); + HTMLFormElement* Form() const { return form_.Get(); } + ValidityState* validity(); + + virtual bool IsFormControlElement() const = 0; + virtual bool IsFormControlElementWithState() const; + virtual bool IsEnumeratable() const = 0; + + // Returns the 'name' attribute value. If this element has no name + // attribute, it returns an empty string instead of null string. + // Note that the 'name' IDL attribute doesn't use this function. + virtual const AtomicString& GetName() const; + + // Override in derived classes to get the encoded name=value pair for + // submitting. + virtual void AppendToFormData(FormData&) {} + + void ResetFormOwner(); + + void FormRemovedFromTree(const Node& form_root); + + // ValidityState attribute implementations + bool CustomError() const; + + // Override functions for patterMismatch, rangeOverflow, rangerUnderflow, + // stepMismatch, tooLong, tooShort and valueMissing must call willValidate + // method. + virtual bool HasBadInput() const; + virtual bool PatternMismatch() const; + virtual bool RangeOverflow() const; + virtual bool RangeUnderflow() const; + virtual bool StepMismatch() const; + virtual bool TooLong() const; + virtual bool TooShort() const; + virtual bool TypeMismatch() const; + virtual bool ValueMissing() const; + virtual String validationMessage() const; + virtual String ValidationSubMessage() const; + bool Valid() const; + virtual void setCustomValidity(const String&); + + void FormAttributeTargetChanged(); + + typedef HeapVector<Member<ListedElement>> List; + + void Trace(blink::Visitor*) override; + + protected: + ListedElement(); + + void InsertedInto(ContainerNode*); + void RemovedFrom(ContainerNode*); + void DidMoveToNewDocument(Document& old_document); + + // FIXME: Remove usage of setForm. resetFormOwner should be enough, and + // setForm is confusing. + void SetForm(HTMLFormElement*); + void AssociateByParser(HTMLFormElement*); + void FormAttributeChanged(); + + // If you add an override of willChangeForm() or didChangeForm() to a class + // derived from this one, you will need to add a call to setForm(0) to the + // destructor of that class. + virtual void WillChangeForm(); + virtual void DidChangeForm(); + + String CustomValidationMessage() const; + + private: + void SetFormAttributeTargetObserver(FormAttributeTargetObserver*); + void ResetFormAttributeTargetObserver(); + + Member<FormAttributeTargetObserver> form_attribute_target_observer_; + Member<HTMLFormElement> form_; + Member<ValidityState> validity_state_; + String custom_validation_message_; + // If m_formWasSetByParser is true, m_form is always non-null. + bool form_was_set_by_parser_; +}; + +CORE_EXPORT HTMLElement* ToHTMLElement(ListedElement*); +CORE_EXPORT HTMLElement& ToHTMLElement(ListedElement&); +CORE_EXPORT const HTMLElement* ToHTMLElement(const ListedElement*); +CORE_EXPORT const HTMLElement& ToHTMLElement(const ListedElement&); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_LISTED_ELEMENT_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc new file mode 100644 index 00000000000..d36dfd4de74 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.cc @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/month_input_type.h" + +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +using namespace HTMLNames; + +static const int kMonthDefaultStep = 1; +static const int kMonthDefaultStepBase = 0; +static const int kMonthStepScaleFactor = 1; + +InputType* MonthInputType::Create(HTMLInputElement& element) { + return new MonthInputType(element); +} + +void MonthInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeMonth); +} + +const AtomicString& MonthInputType::FormControlType() const { + return InputTypeNames::month; +} + +double MonthInputType::ValueAsDate() const { + DateComponents date; + if (!ParseToDateComponents(GetElement().value(), &date)) + return DateComponents::InvalidMilliseconds(); + double msec = date.MillisecondsSinceEpoch(); + DCHECK(std::isfinite(msec)); + return msec; +} + +String MonthInputType::SerializeWithMilliseconds(double value) const { + DateComponents date; + if (!date.SetMillisecondsSinceEpochForMonth(value)) + return String(); + return SerializeWithComponents(date); +} + +Decimal MonthInputType::DefaultValueForStepUp() const { + DateComponents date; + date.SetMillisecondsSinceEpochForMonth(ConvertToLocalTime(CurrentTimeMS())); + double months = date.MonthsSinceEpoch(); + DCHECK(std::isfinite(months)); + return Decimal::FromDouble(months); +} + +StepRange MonthInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL( + const StepRange::StepDescription, step_description, + (kMonthDefaultStep, kMonthDefaultStepBase, kMonthStepScaleFactor, + StepRange::kParsedStepValueShouldBeInteger)); + + return InputType::CreateStepRange( + any_step_handling, Decimal::FromDouble(kMonthDefaultStepBase), + Decimal::FromDouble(DateComponents::MinimumMonth()), + Decimal::FromDouble(DateComponents::MaximumMonth()), step_description); +} + +Decimal MonthInputType::ParseToNumber(const String& src, + const Decimal& default_value) const { + DateComponents date; + if (!ParseToDateComponents(src, &date)) + return default_value; + double months = date.MonthsSinceEpoch(); + DCHECK(std::isfinite(months)); + return Decimal::FromDouble(months); +} + +bool MonthInputType::ParseToDateComponentsInternal(const String& string, + DateComponents* out) const { + DCHECK(out); + unsigned end; + return out->ParseMonth(string, 0, end) && end == string.length(); +} + +bool MonthInputType::SetMillisecondToDateComponents( + double value, + DateComponents* date) const { + DCHECK(date); + return date->SetMonthsSinceEpoch(value); +} + +bool MonthInputType::CanSetSuggestedValue() { + return true; +} + +void MonthInputType::WarnIfValueIsInvalid(const String& value) const { + if (value != GetElement().SanitizeValue(value)) + AddWarningToConsole( + "The specified value %s does not conform to the required format. The " + "format is \"yyyy-MM\" where yyyy is year in four or more digits, and " + "MM is 01-12.", + value); +} + +String MonthInputType::FormatDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) const { + if (!date_time_fields_state.HasMonth() || !date_time_fields_state.HasYear()) + return g_empty_string; + return String::Format("%04u-%02u", date_time_fields_state.Year(), + date_time_fields_state.Month()); +} + +void MonthInputType::SetupLayoutParameters( + DateTimeEditElement::LayoutParameters& layout_parameters, + const DateComponents& date) const { + layout_parameters.date_time_format = layout_parameters.locale.MonthFormat(); + layout_parameters.fallback_date_time_format = "yyyy-MM"; + if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr), + &layout_parameters.minimum)) + layout_parameters.minimum = DateComponents(); + if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr), + &layout_parameters.maximum)) + layout_parameters.maximum = DateComponents(); + layout_parameters.placeholder_for_month = "--"; + layout_parameters.placeholder_for_year = "----"; +} + +bool MonthInputType::IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const { + return has_year && has_month; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h new file mode 100644 index 00000000000..402bc58849d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/month_input_type.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MONTH_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MONTH_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" + +namespace blink { + +class MonthInputType final : public BaseTemporalInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + explicit MonthInputType(HTMLInputElement& element) + : BaseTemporalInputType(element) {} + + void CountUsage() override; + const AtomicString& FormControlType() const override; + double ValueAsDate() const override; + String SerializeWithMilliseconds(double) const override; + Decimal ParseToNumber(const String&, const Decimal&) const override; + Decimal DefaultValueForStepUp() const override; + StepRange CreateStepRange(AnyStepHandling) const override; + bool ParseToDateComponentsInternal(const String&, + DateComponents*) const override; + bool SetMillisecondToDateComponents(double, DateComponents*) const override; + bool CanSetSuggestedValue() override; + void WarnIfValueIsInvalid(const String&) const override; + + // BaseTemporalInputType functions + String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override; + void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&, + const DateComponents&) const override; + bool IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MONTH_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc new file mode 100644 index 00000000000..3481a05ca5d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h" + +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/css_value_keywords.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/focus_controller.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/date_time_format.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" + +namespace blink { + +class DateTimeFormatValidator : public DateTimeFormat::TokenHandler { + public: + DateTimeFormatValidator() + : has_year_(false), + has_month_(false), + has_week_(false), + has_day_(false), + has_ampm_(false), + has_hour_(false), + has_minute_(false), + has_second_(false) {} + + void VisitField(DateTimeFormat::FieldType, int) final; + void VisitLiteral(const String&) final {} + + bool ValidateFormat(const String& format, const BaseTemporalInputType&); + + private: + bool has_year_; + bool has_month_; + bool has_week_; + bool has_day_; + bool has_ampm_; + bool has_hour_; + bool has_minute_; + bool has_second_; +}; + +void DateTimeFormatValidator::VisitField(DateTimeFormat::FieldType field_type, + int) { + switch (field_type) { + case DateTimeFormat::kFieldTypeYear: + has_year_ = true; + break; + case DateTimeFormat::kFieldTypeMonth: // Fallthrough. + case DateTimeFormat::kFieldTypeMonthStandAlone: + has_month_ = true; + break; + case DateTimeFormat::kFieldTypeWeekOfYear: + has_week_ = true; + break; + case DateTimeFormat::kFieldTypeDayOfMonth: + has_day_ = true; + break; + case DateTimeFormat::kFieldTypePeriod: + has_ampm_ = true; + break; + case DateTimeFormat::kFieldTypeHour11: // Fallthrough. + case DateTimeFormat::kFieldTypeHour12: + has_hour_ = true; + break; + case DateTimeFormat::kFieldTypeHour23: // Fallthrough. + case DateTimeFormat::kFieldTypeHour24: + has_hour_ = true; + has_ampm_ = true; + break; + case DateTimeFormat::kFieldTypeMinute: + has_minute_ = true; + break; + case DateTimeFormat::kFieldTypeSecond: + has_second_ = true; + break; + default: + break; + } +} + +bool DateTimeFormatValidator::ValidateFormat( + const String& format, + const BaseTemporalInputType& input_type) { + if (!DateTimeFormat::Parse(format, *this)) + return false; + return input_type.IsValidFormat(has_year_, has_month_, has_week_, has_day_, + has_ampm_, has_hour_, has_minute_, + has_second_); +} + +DateTimeEditElement* +MultipleFieldsTemporalInputTypeView::GetDateTimeEditElement() const { + return ToDateTimeEditElementOrDie( + GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::DateTimeEdit())); +} + +SpinButtonElement* MultipleFieldsTemporalInputTypeView::GetSpinButtonElement() + const { + return ToSpinButtonElementOrDie( + GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::SpinButton())); +} + +ClearButtonElement* MultipleFieldsTemporalInputTypeView::GetClearButtonElement() + const { + return ToClearButtonElementOrDie( + GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::ClearButton())); +} + +PickerIndicatorElement* +MultipleFieldsTemporalInputTypeView::GetPickerIndicatorElement() const { + return ToPickerIndicatorElementOrDie( + GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::PickerIndicator())); +} + +inline bool MultipleFieldsTemporalInputTypeView::ContainsFocusedShadowElement() + const { + return GetElement().UserAgentShadowRoot()->contains( + GetElement().GetDocument().FocusedElement()); +} + +void MultipleFieldsTemporalInputTypeView::DidBlurFromControl( + WebFocusType focus_type) { + // We don't need to call blur(). This function is called when control + // lost focus. + + if (ContainsFocusedShadowElement()) + return; + EventQueueScope scope; + // Remove focus ring by CSS "focus" pseudo class. + GetElement().SetFocused(false, focus_type); + if (SpinButtonElement* spin_button = GetSpinButtonElement()) + spin_button->ReleaseCapture(); +} + +void MultipleFieldsTemporalInputTypeView::DidFocusOnControl( + WebFocusType focus_type) { + // We don't need to call focus(). This function is called when control + // got focus. + + if (!ContainsFocusedShadowElement()) + return; + // Add focus ring by CSS "focus" pseudo class. + // FIXME: Setting the focus flag to non-focused element is too tricky. + GetElement().SetFocused(true, focus_type); +} + +void MultipleFieldsTemporalInputTypeView::EditControlValueChanged() { + String old_value = GetElement().value(); + String new_value = + input_type_->SanitizeValue(GetDateTimeEditElement()->Value()); + // Even if oldValue is null and newValue is "", we should assume they are + // same. + if ((old_value.IsEmpty() && new_value.IsEmpty()) || old_value == new_value) { + GetElement().SetNeedsValidityCheck(); + } else { + GetElement().SetNonAttributeValueByUserEdit(new_value); + GetElement().SetNeedsStyleRecalc( + kSubtreeStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue)); + GetElement().DispatchInputEvent(); + } + GetElement().NotifyFormStateChanged(); + GetElement().UpdateClearButtonVisibility(); +} + +String MultipleFieldsTemporalInputTypeView::FormatDateTimeFieldsState( + const DateTimeFieldsState& state) const { + return input_type_->FormatDateTimeFieldsState(state); +} + +bool MultipleFieldsTemporalInputTypeView::HasCustomFocusLogic() const { + return false; +} + +bool MultipleFieldsTemporalInputTypeView::IsEditControlOwnerDisabled() const { + return GetElement().IsDisabledFormControl(); +} + +bool MultipleFieldsTemporalInputTypeView::IsEditControlOwnerReadOnly() const { + return GetElement().IsReadOnly(); +} + +void MultipleFieldsTemporalInputTypeView::FocusAndSelectSpinButtonOwner() { + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->FocusIfNoFocus(); +} + +bool MultipleFieldsTemporalInputTypeView:: + ShouldSpinButtonRespondToMouseEvents() { + return !GetElement().IsDisabledOrReadOnly(); +} + +bool MultipleFieldsTemporalInputTypeView:: + ShouldSpinButtonRespondToWheelEvents() { + if (!ShouldSpinButtonRespondToMouseEvents()) + return false; + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + return edit->HasFocusedField(); + return false; +} + +void MultipleFieldsTemporalInputTypeView::SpinButtonStepDown() { + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->StepDown(); +} + +void MultipleFieldsTemporalInputTypeView::SpinButtonStepUp() { + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->StepUp(); +} + +void MultipleFieldsTemporalInputTypeView::SpinButtonDidReleaseMouseCapture( + SpinButtonElement::EventDispatch event_dispatch) { + if (event_dispatch == SpinButtonElement::kEventDispatchAllowed) + GetElement().DispatchFormControlChangeEvent(); +} + +bool MultipleFieldsTemporalInputTypeView:: + IsPickerIndicatorOwnerDisabledOrReadOnly() const { + return GetElement().IsDisabledOrReadOnly(); +} + +void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue( + const String& value) { + if (GetElement().IsValidValue(value)) { + GetElement().setValue(value, kDispatchInputAndChangeEvent); + return; + } + + DateTimeEditElement* edit = GetDateTimeEditElement(); + if (!edit) + return; + EventQueueScope scope; + DateComponents date; + unsigned end; + if (date.ParseDate(value, 0, end) && end == value.length()) + edit->SetOnlyYearMonthDay(date); + GetElement().DispatchFormControlChangeEvent(); +} + +void MultipleFieldsTemporalInputTypeView::PickerIndicatorChooseValue( + double value) { + DCHECK(std::isfinite(value) || std::isnan(value)); + if (std::isnan(value)) + GetElement().setValue(g_empty_string, kDispatchInputAndChangeEvent); + else + GetElement().setValueAsNumber(value, ASSERT_NO_EXCEPTION, + kDispatchInputAndChangeEvent); +} + +Element& MultipleFieldsTemporalInputTypeView::PickerOwnerElement() const { + return GetElement(); +} + +bool MultipleFieldsTemporalInputTypeView::SetupDateTimeChooserParameters( + DateTimeChooserParameters& parameters) { + return GetElement().SetupDateTimeChooserParameters(parameters); +} + +MultipleFieldsTemporalInputTypeView::MultipleFieldsTemporalInputTypeView( + HTMLInputElement& element, + BaseTemporalInputType& input_type) + : InputTypeView(element), + input_type_(input_type), + is_destroying_shadow_subtree_(false), + picker_indicator_is_visible_(false), + picker_indicator_is_always_visible_(false) {} + +MultipleFieldsTemporalInputTypeView* +MultipleFieldsTemporalInputTypeView::Create(HTMLInputElement& element, + BaseTemporalInputType& input_type) { + return new MultipleFieldsTemporalInputTypeView(element, input_type); +} + +MultipleFieldsTemporalInputTypeView::~MultipleFieldsTemporalInputTypeView() = + default; + +void MultipleFieldsTemporalInputTypeView::Trace(blink::Visitor* visitor) { + visitor->Trace(input_type_); + InputTypeView::Trace(visitor); +} + +void MultipleFieldsTemporalInputTypeView::Blur() { + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->BlurByOwner(); +} + +scoped_refptr<ComputedStyle> +MultipleFieldsTemporalInputTypeView::CustomStyleForLayoutObject( + scoped_refptr<ComputedStyle> original_style) { + EDisplay original_display = original_style->Display(); + EDisplay new_display = original_display; + if (original_display == EDisplay::kInline || + original_display == EDisplay::kInlineBlock) + new_display = EDisplay::kInlineFlex; + else if (original_display == EDisplay::kBlock) + new_display = EDisplay::kFlex; + TextDirection content_direction = ComputedTextDirection(); + if (original_style->Direction() == content_direction && + original_display == new_display) + return original_style; + + scoped_refptr<ComputedStyle> style = ComputedStyle::Clone(*original_style); + style->SetDirection(content_direction); + style->SetDisplay(new_display); + style->SetUnique(); + return style; +} + +void MultipleFieldsTemporalInputTypeView::CreateShadowSubtree() { + DCHECK(IsShadowHost(GetElement())); + + // Element must not have a layoutObject here, because if it did + // DateTimeEditElement::customStyleForLayoutObject() is called in + // appendChild() before the field wrapper element is created. + // FIXME: This code should not depend on such craziness. + DCHECK(!GetElement().GetLayoutObject()); + + Document& document = GetElement().GetDocument(); + ContainerNode* container = GetElement().UserAgentShadowRoot(); + + container->AppendChild(DateTimeEditElement::Create(document, *this)); + GetElement().UpdateView(); + container->AppendChild(ClearButtonElement::Create(document, *this)); + container->AppendChild(SpinButtonElement::Create(document, *this)); + + if (LayoutTheme::GetTheme().SupportsCalendarPicker( + input_type_->FormControlType())) + picker_indicator_is_always_visible_ = true; + container->AppendChild(PickerIndicatorElement::Create(document, *this)); + picker_indicator_is_visible_ = true; + UpdatePickerIndicatorVisibility(); +} + +void MultipleFieldsTemporalInputTypeView::DestroyShadowSubtree() { + DCHECK(!is_destroying_shadow_subtree_); + is_destroying_shadow_subtree_ = true; + if (SpinButtonElement* element = GetSpinButtonElement()) + element->RemoveSpinButtonOwner(); + if (ClearButtonElement* element = GetClearButtonElement()) + element->RemoveClearButtonOwner(); + if (DateTimeEditElement* element = GetDateTimeEditElement()) + element->RemoveEditControlOwner(); + if (PickerIndicatorElement* element = GetPickerIndicatorElement()) + element->RemovePickerIndicatorOwner(); + + // If a field element has focus, set focus back to the <input> itself before + // deleting the field. This prevents unnecessary focusout/blur events. + if (ContainsFocusedShadowElement()) + GetElement().focus(); + + InputTypeView::DestroyShadowSubtree(); + is_destroying_shadow_subtree_ = false; +} + +void MultipleFieldsTemporalInputTypeView::HandleClickEvent(MouseEvent* event) { + if (!event->isTrusted()) { + UseCounter::Count(GetElement().GetDocument(), + WebFeature::kTemporalInputTypeIgnoreUntrustedClick); + } +} + +void MultipleFieldsTemporalInputTypeView::HandleFocusInEvent( + Element* old_focused_element, + WebFocusType type) { + DateTimeEditElement* edit = GetDateTimeEditElement(); + if (!edit || is_destroying_shadow_subtree_) + return; + if (type == kWebFocusTypeBackward) { + if (GetElement().GetDocument().GetPage()) + GetElement().GetDocument().GetPage()->GetFocusController().AdvanceFocus( + type); + } else if (type == kWebFocusTypeNone || type == kWebFocusTypeMouse || + type == kWebFocusTypePage) { + edit->FocusByOwner(old_focused_element); + } else { + edit->FocusByOwner(); + } +} + +void MultipleFieldsTemporalInputTypeView::ForwardEvent(Event* event) { + if (SpinButtonElement* element = GetSpinButtonElement()) { + element->ForwardEvent(event); + if (event->DefaultHandled()) + return; + } + + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->DefaultEventHandler(event); +} + +void MultipleFieldsTemporalInputTypeView::DisabledAttributeChanged() { + EventQueueScope scope; + GetSpinButtonElement()->ReleaseCapture(); + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->DisabledStateChanged(); +} + +void MultipleFieldsTemporalInputTypeView::RequiredAttributeChanged() { + UpdateClearButtonVisibility(); +} + +void MultipleFieldsTemporalInputTypeView::HandleKeydownEvent( + KeyboardEvent* event) { + if (!GetElement().IsFocused()) + return; + if (picker_indicator_is_visible_ && + ((event->key() == "ArrowDown" && event->getModifierState("Alt")) || + (LayoutTheme::GetTheme().ShouldOpenPickerWithF4Key() && + event->key() == "F4"))) { + if (PickerIndicatorElement* element = GetPickerIndicatorElement()) + element->OpenPopup(); + event->SetDefaultHandled(); + } else { + ForwardEvent(event); + } +} + +bool MultipleFieldsTemporalInputTypeView::HasBadInput() const { + DateTimeEditElement* edit = GetDateTimeEditElement(); + return GetElement().value().IsEmpty() && edit && + edit->AnyEditableFieldsHaveValues(); +} + +AtomicString MultipleFieldsTemporalInputTypeView::LocaleIdentifier() const { + return GetElement().ComputeInheritedLanguage(); +} + +void MultipleFieldsTemporalInputTypeView:: + EditControlDidChangeValueByKeyboard() { + GetElement().DispatchFormControlChangeEvent(); +} + +void MultipleFieldsTemporalInputTypeView::MinOrMaxAttributeChanged() { + UpdateView(); +} + +void MultipleFieldsTemporalInputTypeView::ReadonlyAttributeChanged() { + EventQueueScope scope; + GetSpinButtonElement()->ReleaseCapture(); + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + edit->ReadOnlyStateChanged(); +} + +void MultipleFieldsTemporalInputTypeView::RestoreFormControlState( + const FormControlState& state) { + DateTimeEditElement* edit = GetDateTimeEditElement(); + if (!edit) + return; + DateTimeFieldsState date_time_fields_state = + DateTimeFieldsState::RestoreFormControlState(state); + edit->SetValueAsDateTimeFieldsState(date_time_fields_state); + GetElement().SetNonAttributeValue(input_type_->SanitizeValue(edit->Value())); + UpdateClearButtonVisibility(); +} + +FormControlState MultipleFieldsTemporalInputTypeView::SaveFormControlState() + const { + if (DateTimeEditElement* edit = GetDateTimeEditElement()) + return edit->ValueAsDateTimeFieldsState().SaveFormControlState(); + return FormControlState(); +} + +void MultipleFieldsTemporalInputTypeView::DidSetValue( + const String& sanitized_value, + bool value_changed) { + DateTimeEditElement* edit = GetDateTimeEditElement(); + if (value_changed || (sanitized_value.IsEmpty() && edit && + edit->AnyEditableFieldsHaveValues())) { + GetElement().UpdateView(); + GetElement().SetNeedsValidityCheck(); + } +} + +void MultipleFieldsTemporalInputTypeView::StepAttributeChanged() { + UpdateView(); +} + +void MultipleFieldsTemporalInputTypeView::UpdateView() { + DateTimeEditElement* edit = GetDateTimeEditElement(); + if (!edit) + return; + + DateTimeEditElement::LayoutParameters layout_parameters( + GetElement().GetLocale(), + input_type_->CreateStepRange(kAnyIsDefaultStep)); + + DateComponents date; + bool has_value = false; + if (!GetElement().SuggestedValue().IsNull()) + has_value = input_type_->ParseToDateComponents( + GetElement().SuggestedValue(), &date); + else + has_value = input_type_->ParseToDateComponents(GetElement().value(), &date); + if (!has_value) + input_type_->SetMillisecondToDateComponents( + layout_parameters.step_range.Minimum().ToDouble(), &date); + + input_type_->SetupLayoutParameters(layout_parameters, date); + + DEFINE_STATIC_LOCAL(AtomicString, datetimeformat_attr, ("datetimeformat")); + edit->setAttribute(datetimeformat_attr, + AtomicString(layout_parameters.date_time_format), + ASSERT_NO_EXCEPTION); + const AtomicString pattern = edit->FastGetAttribute(HTMLNames::patternAttr); + if (!pattern.IsEmpty()) + layout_parameters.date_time_format = pattern; + + if (!DateTimeFormatValidator().ValidateFormat( + layout_parameters.date_time_format, *input_type_)) + layout_parameters.date_time_format = + layout_parameters.fallback_date_time_format; + + if (has_value) + edit->SetValueAsDate(layout_parameters, date); + else + edit->SetEmptyValue(layout_parameters, date); + UpdateClearButtonVisibility(); +} + +void MultipleFieldsTemporalInputTypeView::ClosePopupView() { + if (PickerIndicatorElement* picker = GetPickerIndicatorElement()) + picker->ClosePopup(); +} + +void MultipleFieldsTemporalInputTypeView::ValueAttributeChanged() { + if (!GetElement().HasDirtyValue()) + UpdateView(); +} + +void MultipleFieldsTemporalInputTypeView::ListAttributeTargetChanged() { + UpdatePickerIndicatorVisibility(); +} + +void MultipleFieldsTemporalInputTypeView::UpdatePickerIndicatorVisibility() { + if (picker_indicator_is_always_visible_) { + ShowPickerIndicator(); + return; + } + if (GetElement().HasValidDataListOptions()) + ShowPickerIndicator(); + else + HidePickerIndicator(); +} + +void MultipleFieldsTemporalInputTypeView::HidePickerIndicator() { + if (!picker_indicator_is_visible_) + return; + picker_indicator_is_visible_ = false; + DCHECK(GetPickerIndicatorElement()); + GetPickerIndicatorElement()->SetInlineStyleProperty(CSSPropertyDisplay, + CSSValueNone); +} + +void MultipleFieldsTemporalInputTypeView::ShowPickerIndicator() { + if (picker_indicator_is_visible_) + return; + picker_indicator_is_visible_ = true; + DCHECK(GetPickerIndicatorElement()); + GetPickerIndicatorElement()->RemoveInlineStyleProperty(CSSPropertyDisplay); +} + +void MultipleFieldsTemporalInputTypeView::FocusAndSelectClearButtonOwner() { + GetElement().focus(); +} + +bool MultipleFieldsTemporalInputTypeView:: + ShouldClearButtonRespondToMouseEvents() { + return !GetElement().IsDisabledOrReadOnly() && !GetElement().IsRequired(); +} + +void MultipleFieldsTemporalInputTypeView::ClearValue() { + GetElement().setValue("", kDispatchInputAndChangeEvent); + GetElement().UpdateClearButtonVisibility(); +} + +void MultipleFieldsTemporalInputTypeView::UpdateClearButtonVisibility() { + ClearButtonElement* clear_button = GetClearButtonElement(); + if (!clear_button) + return; + + if (GetElement().IsRequired() || + !GetDateTimeEditElement()->AnyEditableFieldsHaveValues()) { + clear_button->SetInlineStyleProperty(CSSPropertyOpacity, 0.0, + CSSPrimitiveValue::UnitType::kNumber); + clear_button->SetInlineStyleProperty(CSSPropertyPointerEvents, + CSSValueNone); + } else { + clear_button->RemoveInlineStyleProperty(CSSPropertyOpacity); + clear_button->RemoveInlineStyleProperty(CSSPropertyPointerEvents); + } +} + +TextDirection MultipleFieldsTemporalInputTypeView::ComputedTextDirection() { + return GetElement().GetLocale().IsRTL() ? TextDirection::kRtl + : TextDirection::kLtr; +} + +AXObject* MultipleFieldsTemporalInputTypeView::PopupRootAXObject() { + if (PickerIndicatorElement* picker = GetPickerIndicatorElement()) + return picker->PopupRootAXObject(); + return nullptr; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h new file mode 100644 index 00000000000..b60ffbe75fc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MULTIPLE_FIELDS_TEMPORAL_INPUT_TYPE_VIEW_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MULTIPLE_FIELDS_TEMPORAL_INPUT_TYPE_VIEW_H_ + +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/renderer/core/html/forms/clear_button_element.h" +#include "third_party/blink/renderer/core/html/forms/date_time_edit_element.h" +#include "third_party/blink/renderer/core/html/forms/input_type_view.h" +#include "third_party/blink/renderer/core/html/forms/picker_indicator_element.h" +#include "third_party/blink/renderer/core/html/forms/spin_button_element.h" + +namespace blink { + +class BaseTemporalInputType; +struct DateTimeChooserParameters; + +class MultipleFieldsTemporalInputTypeView final + : public GarbageCollectedFinalized<MultipleFieldsTemporalInputTypeView>, + public InputTypeView, + protected DateTimeEditElement::EditControlOwner, + protected PickerIndicatorElement::PickerIndicatorOwner, + protected SpinButtonElement::SpinButtonOwner, + protected ClearButtonElement::ClearButtonOwner { + USING_GARBAGE_COLLECTED_MIXIN(MultipleFieldsTemporalInputTypeView); + + public: + static MultipleFieldsTemporalInputTypeView* Create(HTMLInputElement&, + BaseTemporalInputType&); + ~MultipleFieldsTemporalInputTypeView() override; + void Trace(blink::Visitor*) override; + + private: + MultipleFieldsTemporalInputTypeView(HTMLInputElement&, + BaseTemporalInputType&); + + // DateTimeEditElement::EditControlOwner functions + void DidBlurFromControl(WebFocusType) final; + void DidFocusOnControl(WebFocusType) final; + void EditControlValueChanged() final; + String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override; + bool IsEditControlOwnerDisabled() const final; + bool IsEditControlOwnerReadOnly() const final; + AtomicString LocaleIdentifier() const final; + void EditControlDidChangeValueByKeyboard() final; + + // SpinButtonElement::SpinButtonOwner functions. + void FocusAndSelectSpinButtonOwner() override; + bool ShouldSpinButtonRespondToMouseEvents() override; + bool ShouldSpinButtonRespondToWheelEvents() override; + void SpinButtonStepDown() override; + void SpinButtonStepUp() override; + void SpinButtonDidReleaseMouseCapture( + SpinButtonElement::EventDispatch) override; + + // PickerIndicatorElement::PickerIndicatorOwner functions + bool IsPickerIndicatorOwnerDisabledOrReadOnly() const final; + void PickerIndicatorChooseValue(const String&) final; + void PickerIndicatorChooseValue(double) final; + Element& PickerOwnerElement() const final; + bool SetupDateTimeChooserParameters(DateTimeChooserParameters&) final; + + // ClearButtonElement::ClearButtonOwner functions. + void FocusAndSelectClearButtonOwner() override; + bool ShouldClearButtonRespondToMouseEvents() override; + void ClearValue() override; + + // InputTypeView functions + void Blur() final; + void ClosePopupView() override; + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject( + scoped_refptr<ComputedStyle>) override; + void CreateShadowSubtree() final; + void DestroyShadowSubtree() final; + void DisabledAttributeChanged() final; + void ForwardEvent(Event*) final; + void HandleClickEvent(MouseEvent*) final; + void HandleFocusInEvent(Element* old_focused_element, WebFocusType) final; + void HandleKeydownEvent(KeyboardEvent*) final; + bool HasBadInput() const override; + bool HasCustomFocusLogic() const final; + void MinOrMaxAttributeChanged() final; + void ReadonlyAttributeChanged() final; + void RequiredAttributeChanged() final; + void RestoreFormControlState(const FormControlState&) final; + FormControlState SaveFormControlState() const final; + void DidSetValue(const String&, bool value_changed) final; + void StepAttributeChanged() final; + void UpdateView() final; + void ValueAttributeChanged() override; + void ListAttributeTargetChanged() final; + void UpdateClearButtonVisibility() final; + TextDirection ComputedTextDirection() final; + AXObject* PopupRootAXObject() final; + + DateTimeEditElement* GetDateTimeEditElement() const; + SpinButtonElement* GetSpinButtonElement() const; + ClearButtonElement* GetClearButtonElement() const; + PickerIndicatorElement* GetPickerIndicatorElement() const; + bool ContainsFocusedShadowElement() const; + void ShowPickerIndicator(); + void HidePickerIndicator(); + void UpdatePickerIndicatorVisibility(); + + Member<BaseTemporalInputType> input_type_; + bool is_destroying_shadow_subtree_; + bool picker_indicator_is_visible_; + bool picker_indicator_is_always_visible_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MULTIPLE_FIELDS_TEMPORAL_INPUT_TYPE_VIEW_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc new file mode 100644 index 00000000000..e7bfdd2c5bd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.cc @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/number_input_type.h" + +#include <limits> +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/events/before_text_inserted_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/core/layout/layout_object.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" + +namespace blink { + +using blink::WebLocalizedString; +using namespace HTMLNames; + +static const int kNumberDefaultStep = 1; +static const int kNumberDefaultStepBase = 0; +static const int kNumberStepScaleFactor = 1; + +struct RealNumberRenderSize { + unsigned size_before_decimal_point; + unsigned size_afte_decimal_point; + + RealNumberRenderSize(unsigned before, unsigned after) + : size_before_decimal_point(before), size_afte_decimal_point(after) {} + + RealNumberRenderSize Max(const RealNumberRenderSize& other) const { + return RealNumberRenderSize( + std::max(size_before_decimal_point, other.size_before_decimal_point), + std::max(size_afte_decimal_point, other.size_afte_decimal_point)); + } +}; + +static RealNumberRenderSize CalculateRenderSize(const Decimal& value) { + DCHECK(value.IsFinite()); + const unsigned size_of_digits = + String::Number(value.Value().Coefficient()).length(); + const unsigned size_of_sign = value.IsNegative() ? 1 : 0; + const int exponent = value.Exponent(); + if (exponent >= 0) + return RealNumberRenderSize(size_of_sign + size_of_digits, 0); + + const int size_before_decimal_point = exponent + size_of_digits; + if (size_before_decimal_point > 0) { + // In case of "123.456" + return RealNumberRenderSize(size_of_sign + size_before_decimal_point, + size_of_digits - size_before_decimal_point); + } + + // In case of "0.00012345" + const unsigned kSizeOfZero = 1; + const unsigned number_of_zero_after_decimal_point = + -size_before_decimal_point; + return RealNumberRenderSize( + size_of_sign + kSizeOfZero, + number_of_zero_after_decimal_point + size_of_digits); +} + +InputType* NumberInputType::Create(HTMLInputElement& element) { + return new NumberInputType(element); +} + +void NumberInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeNumber); +} + +const AtomicString& NumberInputType::FormControlType() const { + return InputTypeNames::number; +} + +void NumberInputType::SetValue(const String& sanitized_value, + bool value_changed, + TextFieldEventBehavior event_behavior, + TextControlSetValueSelection selection) { + if (!value_changed && sanitized_value.IsEmpty() && + !GetElement().InnerEditorValue().IsEmpty()) + GetElement().UpdateView(); + TextFieldInputType::SetValue(sanitized_value, value_changed, event_behavior, + selection); +} + +double NumberInputType::ValueAsDouble() const { + return ParseToDoubleForNumberType(GetElement().value()); +} + +void NumberInputType::SetValueAsDouble(double new_value, + TextFieldEventBehavior event_behavior, + ExceptionState& exception_state) const { + GetElement().setValue(SerializeForNumberType(new_value), event_behavior); +} + +void NumberInputType::SetValueAsDecimal(const Decimal& new_value, + TextFieldEventBehavior event_behavior, + ExceptionState& exception_state) const { + GetElement().setValue(SerializeForNumberType(new_value), event_behavior); +} + +bool NumberInputType::TypeMismatchFor(const String& value) const { + return !value.IsEmpty() && !std::isfinite(ParseToDoubleForNumberType(value)); +} + +bool NumberInputType::TypeMismatch() const { + DCHECK(!TypeMismatchFor(GetElement().value())); + return false; +} + +StepRange NumberInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL( + const StepRange::StepDescription, step_description, + (kNumberDefaultStep, kNumberDefaultStepBase, kNumberStepScaleFactor)); + const Decimal double_max = + Decimal::FromDouble(std::numeric_limits<double>::max()); + return InputType::CreateStepRange(any_step_handling, kNumberDefaultStepBase, + -double_max, double_max, step_description); +} + +bool NumberInputType::SizeShouldIncludeDecoration(int default_size, + int& preferred_size) const { + preferred_size = default_size; + + const String step_string = GetElement().FastGetAttribute(stepAttr); + if (DeprecatedEqualIgnoringCase(step_string, "any")) + return false; + + const Decimal minimum = + ParseToDecimalForNumberType(GetElement().FastGetAttribute(minAttr)); + if (!minimum.IsFinite()) + return false; + + const Decimal maximum = + ParseToDecimalForNumberType(GetElement().FastGetAttribute(maxAttr)); + if (!maximum.IsFinite()) + return false; + + const Decimal step = ParseToDecimalForNumberType(step_string, 1); + DCHECK(step.IsFinite()); + + RealNumberRenderSize size = CalculateRenderSize(minimum).Max( + CalculateRenderSize(maximum).Max(CalculateRenderSize(step))); + + preferred_size = size.size_before_decimal_point + + size.size_afte_decimal_point + + (size.size_afte_decimal_point ? 1 : 0); + + return true; +} + +bool NumberInputType::IsSteppable() const { + return true; +} + +void NumberInputType::HandleKeydownEvent(KeyboardEvent* event) { + EventQueueScope scope; + HandleKeydownEventForSpinButton(event); + if (!event->DefaultHandled()) + TextFieldInputType::HandleKeydownEvent(event); +} + +void NumberInputType::HandleBeforeTextInsertedEvent( + BeforeTextInsertedEvent* event) { + event->SetText(GetLocale().StripInvalidNumberCharacters(event->GetText(), + "0123456789.Ee-+")); +} + +Decimal NumberInputType::ParseToNumber(const String& src, + const Decimal& default_value) const { + return ParseToDecimalForNumberType(src, default_value); +} + +String NumberInputType::Serialize(const Decimal& value) const { + if (!value.IsFinite()) + return String(); + return SerializeForNumberType(value); +} + +static bool IsE(UChar ch) { + return ch == 'e' || ch == 'E'; +} + +String NumberInputType::LocalizeValue(const String& proposed_value) const { + if (proposed_value.IsEmpty()) + return proposed_value; + // We don't localize scientific notations. + if (proposed_value.Find(IsE) != kNotFound) + return proposed_value; + return GetElement().GetLocale().ConvertToLocalizedNumber(proposed_value); +} + +String NumberInputType::VisibleValue() const { + return LocalizeValue(GetElement().value()); +} + +String NumberInputType::ConvertFromVisibleValue( + const String& visible_value) const { + if (visible_value.IsEmpty()) + return visible_value; + // We don't localize scientific notations. + if (visible_value.Find(IsE) != kNotFound) + return visible_value; + return GetElement().GetLocale().ConvertFromLocalizedNumber(visible_value); +} + +String NumberInputType::SanitizeValue(const String& proposed_value) const { + if (proposed_value.IsEmpty()) + return proposed_value; + return std::isfinite(ParseToDoubleForNumberType(proposed_value)) + ? proposed_value + : g_empty_string; +} + +void NumberInputType::WarnIfValueIsInvalid(const String& value) const { + if (value.IsEmpty() || !GetElement().SanitizeValue(value).IsEmpty()) + return; + AddWarningToConsole( + "The specified value %s is not a valid number. The value must match to " + "the following regular expression: " + "-?(\\d+|\\d+\\.\\d+|\\.\\d+)([eE][-+]?\\d+)?", + value); +} + +bool NumberInputType::HasBadInput() const { + String standard_value = + ConvertFromVisibleValue(GetElement().InnerEditorValue()); + return !standard_value.IsEmpty() && + !std::isfinite(ParseToDoubleForNumberType(standard_value)); +} + +String NumberInputType::BadInputText() const { + return GetLocale().QueryString( + WebLocalizedString::kValidationBadInputForNumber); +} + +String NumberInputType::RangeOverflowText(const Decimal& maximum) const { + return GetLocale().QueryString(WebLocalizedString::kValidationRangeOverflow, + LocalizeValue(Serialize(maximum))); +} + +String NumberInputType::RangeUnderflowText(const Decimal& minimum) const { + return GetLocale().QueryString(WebLocalizedString::kValidationRangeUnderflow, + LocalizeValue(Serialize(minimum))); +} + +bool NumberInputType::SupportsPlaceholder() const { + return true; +} + +void NumberInputType::MinOrMaxAttributeChanged() { + TextFieldInputType::MinOrMaxAttributeChanged(); + + if (GetElement().GetLayoutObject()) + GetElement() + .GetLayoutObject() + ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + LayoutInvalidationReason::kAttributeChanged); +} + +void NumberInputType::StepAttributeChanged() { + TextFieldInputType::StepAttributeChanged(); + + if (GetElement().GetLayoutObject()) + GetElement() + .GetLayoutObject() + ->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( + LayoutInvalidationReason::kAttributeChanged); +} + +bool NumberInputType::SupportsSelectionAPI() const { + return false; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/number_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.h new file mode 100644 index 00000000000..302539b63a0 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/number_input_type.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_NUMBER_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_NUMBER_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/text_field_input_type.h" + +namespace blink { + +class ExceptionState; + +class NumberInputType final : public TextFieldInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + NumberInputType(HTMLInputElement& element) : TextFieldInputType(element) {} + void CountUsage() override; + const AtomicString& FormControlType() const override; + void SetValue(const String&, + bool value_changed, + TextFieldEventBehavior, + TextControlSetValueSelection) override; + double ValueAsDouble() const override; + void SetValueAsDouble(double, + TextFieldEventBehavior, + ExceptionState&) const override; + void SetValueAsDecimal(const Decimal&, + TextFieldEventBehavior, + ExceptionState&) const override; + bool TypeMismatchFor(const String&) const override; + bool TypeMismatch() const override; + bool SizeShouldIncludeDecoration(int default_size, + int& preferred_size) const override; + bool IsSteppable() const override; + StepRange CreateStepRange(AnyStepHandling) const override; + void HandleKeydownEvent(KeyboardEvent*) override; + void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) override; + Decimal ParseToNumber(const String&, const Decimal&) const override; + String Serialize(const Decimal&) const override; + String LocalizeValue(const String&) const override; + String VisibleValue() const override; + String ConvertFromVisibleValue(const String&) const override; + String SanitizeValue(const String&) const override; + void WarnIfValueIsInvalid(const String&) const override; + bool HasBadInput() const override; + String BadInputText() const override; + String RangeOverflowText(const Decimal& maxmum) const override; + String RangeUnderflowText(const Decimal& minimum) const override; + bool SupportsPlaceholder() const override; + void MinOrMaxAttributeChanged() override; + void StepAttributeChanged() override; + bool SupportsSelectionAPI() const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_NUMBER_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/option_list.cc b/chromium/third_party/blink/renderer/core/html/forms/option_list.cc new file mode 100644 index 00000000000..5beee4e934c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/option_list.cc @@ -0,0 +1,40 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/option_list.h" + +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" + +namespace blink { + +void OptionListIterator::Advance(HTMLOptionElement* previous) { + // This function returns only + // - An OPTION child of m_select, or + // - An OPTION child of an OPTGROUP child of m_select. + + Element* current; + if (previous) { + DCHECK_EQ(previous->OwnerSelectElement(), select_); + current = ElementTraversal::NextSkippingChildren(*previous, select_); + } else { + current = ElementTraversal::FirstChild(*select_); + } + while (current) { + if (auto* option = ToHTMLOptionElementOrNull(current)) { + current_ = option; + return; + } + if (IsHTMLOptGroupElement(current) && + current->parentNode() == select_.Get()) { + if ((current_ = Traversal<HTMLOptionElement>::FirstChild(*current))) + return; + } + current = ElementTraversal::NextSkippingChildren(*current, select_); + } + current_ = nullptr; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/option_list.h b/chromium/third_party/blink/renderer/core/html/forms/option_list.h new file mode 100644 index 00000000000..cd3ea21d4e5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/option_list.h @@ -0,0 +1,60 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_OPTION_LIST_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_OPTION_LIST_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + +namespace blink { + +class HTMLSelectElement; +class HTMLOptionElement; + +class CORE_EXPORT OptionListIterator final { + STACK_ALLOCATED(); + + public: + explicit OptionListIterator(const HTMLSelectElement* select) + : select_(select) { + if (select_) + Advance(nullptr); + } + HTMLOptionElement* operator*() { return current_; } + void operator++() { + if (current_) + Advance(current_); + } + bool operator==(const OptionListIterator& other) const { + return current_ == other.current_; + } + bool operator!=(const OptionListIterator& other) const { + return !(*this == other); + } + + private: + void Advance(HTMLOptionElement* current); + + Member<const HTMLSelectElement> select_; + Member<HTMLOptionElement> current_; // nullptr means we reached to the end. +}; + +// OptionList class is a lightweight version of HTMLOptionsCollection. +class OptionList final { + STACK_ALLOCATED(); + + public: + explicit OptionList(const HTMLSelectElement& select) : select_(select) {} + using Iterator = OptionListIterator; + Iterator begin() { return Iterator(select_); } + Iterator end() { return Iterator(nullptr); } + + private: + Member<const HTMLSelectElement> select_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc b/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc new file mode 100644 index 00000000000..f10e65be341 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/option_list_test.cc @@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/option_list.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" +#include "third_party/blink/renderer/core/html/html_document.h" + +namespace blink { + +namespace { + +AtomicString Id(const HTMLOptionElement* option) { + return option->FastGetAttribute(HTMLNames::idAttr); +} + +} // namespace + +class OptionListTest : public testing::Test { + protected: + void SetUp() override { + HTMLDocument* document = HTMLDocument::CreateForTest(); + HTMLSelectElement* select = HTMLSelectElement::Create(*document); + document->AppendChild(select); + select_ = select; + } + HTMLSelectElement& Select() const { return *select_; } + + private: + Persistent<HTMLSelectElement> select_; +}; + +TEST_F(OptionListTest, Empty) { + OptionList list = Select().GetOptionList(); + EXPECT_EQ(list.end(), list.begin()) + << "OptionList should iterate over empty SELECT successfully"; +} + +TEST_F(OptionListTest, OptionOnly) { + Select().SetInnerHTMLFromString( + "text<input><option id=o1></option><input><option " + "id=o2></option><input>"); + auto* div = + ToHTMLElement(Select().GetDocument().CreateRawElement(HTMLNames::divTag)); + div->SetInnerHTMLFromString("<option id=o3></option>"); + Select().AppendChild(div); + OptionList list = Select().GetOptionList(); + OptionList::Iterator iter = list.begin(); + EXPECT_EQ("o1", Id(*iter)); + ++iter; + EXPECT_EQ("o2", Id(*iter)); + ++iter; + // No "o3" because it's in DIV. + EXPECT_EQ(list.end(), iter); +} + +TEST_F(OptionListTest, Optgroup) { + Select().SetInnerHTMLFromString( + "<optgroup><option id=g11></option><option id=g12></option></optgroup>" + "<optgroup><option id=g21></option></optgroup>" + "<optgroup></optgroup>" + "<option id=o1></option>" + "<optgroup><option id=g41></option></optgroup>"); + OptionList list = Select().GetOptionList(); + OptionList::Iterator iter = list.begin(); + EXPECT_EQ("g11", Id(*iter)); + ++iter; + EXPECT_EQ("g12", Id(*iter)); + ++iter; + EXPECT_EQ("g21", Id(*iter)); + ++iter; + EXPECT_EQ("o1", Id(*iter)); + ++iter; + EXPECT_EQ("g41", Id(*iter)); + ++iter; + EXPECT_EQ(list.end(), iter); + + ToHTMLElement(Select().firstChild()) + ->SetInnerHTMLFromString( + "<optgroup><option id=gg11></option></optgroup>" + "<option id=g11></option>"); + list = Select().GetOptionList(); + iter = list.begin(); + EXPECT_EQ("g11", Id(*iter)) << "Nested OPTGROUP should be ignored."; +} + +} // naemespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc new file mode 100644 index 00000000000..82bcbf68fa6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.cc @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/password_input_type.h" + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/form_controller.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_text_control_single_line.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" + +namespace blink { + +InputType* PasswordInputType::Create(HTMLInputElement& element) { + return new PasswordInputType(element); +} + +void PasswordInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypePassword); + if (GetElement().FastHasAttribute(HTMLNames::maxlengthAttr)) + CountUsageIfVisible(WebFeature::kInputTypePasswordMaxLength); +} + +const AtomicString& PasswordInputType::FormControlType() const { + return InputTypeNames::password; +} + +bool PasswordInputType::ShouldSaveAndRestoreFormControlState() const { + return false; +} + +FormControlState PasswordInputType::SaveFormControlState() const { + // Should never save/restore password fields. + NOTREACHED(); + return FormControlState(); +} + +void PasswordInputType::RestoreFormControlState(const FormControlState&) { + // Should never save/restore password fields. + NOTREACHED(); +} + +bool PasswordInputType::ShouldRespectListAttribute() { + return false; +} + +void PasswordInputType::OnAttachWithLayoutObject() { + GetElement().GetDocument().IncrementPasswordCount(); +} + +void PasswordInputType::OnDetachWithLayoutObject() { + GetElement().GetDocument().DecrementPasswordCount(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/password_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.h new file mode 100644 index 00000000000..093a7947405 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PASSWORD_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PASSWORD_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h" + +namespace blink { + +class PasswordInputType final : public BaseTextInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + explicit PasswordInputType(HTMLInputElement& element) + : BaseTextInputType(element) {} + void CountUsage() override; + const AtomicString& FormControlType() const override; + bool ShouldSaveAndRestoreFormControlState() const override; + FormControlState SaveFormControlState() const override; + void RestoreFormControlState(const FormControlState&) override; + bool ShouldRespectListAttribute() override; + void OnAttachWithLayoutObject() override; + void OnDetachWithLayoutObject() override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PASSWORD_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc b/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc new file mode 100644 index 00000000000..65e28107e7a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/password_input_type_test.cc @@ -0,0 +1,353 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include <memory> +#include <utility> + +#include "third_party/blink/renderer/core/html/forms/password_input_type.h" + +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/service_manager/public/cpp/interface_provider.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/modules/insecure_input/insecure_input_service.mojom-blink.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" + +namespace blink { + +class MockInsecureInputService : public mojom::blink::InsecureInputService { + public: + explicit MockInsecureInputService(LocalFrame& frame) { + service_manager::InterfaceProvider::TestApi test_api( + &frame.GetInterfaceProvider()); + test_api.SetBinderForName( + mojom::blink::InsecureInputService::Name_, + WTF::BindRepeating(&MockInsecureInputService::BindRequest, + WTF::Unretained(this))); + } + + ~MockInsecureInputService() override = default; + + void BindRequest(mojo::ScopedMessagePipeHandle handle) { + binding_set_.AddBinding( + this, mojom::blink::InsecureInputServiceRequest(std::move(handle))); + } + + unsigned DidEditFieldCalls() const { return num_did_edit_field_calls_; } + + bool PasswordFieldVisibleCalled() const { + return password_field_visible_called_; + } + + unsigned NumPasswordFieldsInvisibleCalls() const { + return num_password_fields_invisible_calls_; + } + + private: + // mojom::InsecureInputService + void PasswordFieldVisibleInInsecureContext() override { + password_field_visible_called_ = true; + } + + void AllPasswordFieldsInInsecureContextInvisible() override { + ++num_password_fields_invisible_calls_; + } + + void DidEditFieldInInsecureContext() override { ++num_did_edit_field_calls_; } + + mojo::BindingSet<InsecureInputService> binding_set_; + + bool password_field_visible_called_ = false; + unsigned num_did_edit_field_calls_ = 0; + unsigned num_password_fields_invisible_calls_ = 0; +}; + +// Tests that a Mojo message is sent when a visible password field +// appears on the page. +TEST(PasswordInputTypeTest, PasswordVisibilityEvent) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled()); +} + +// Tests that a Mojo message is not sent when a visible password field +// appears in a secure context. +TEST(PasswordInputTypeTest, PasswordVisibilityEventInSecureContext) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().SetURL(KURL("https://example.test")); + page_holder->GetDocument().SetSecurityOrigin( + SecurityOrigin::Create(KURL("https://example.test"))); + page_holder->GetDocument().SetSecureContextStateForTesting( + SecureContextState::kSecure); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + // No message should have been sent from a secure context. + blink::test::RunPendingTasks(); + EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled()); +} + +// Tests that a Mojo message is sent when a previously invisible password field +// becomes visible. +TEST(PasswordInputTypeTest, InvisiblePasswordFieldBecomesVisible) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password' style='display:none;'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + // The message should not be sent for a hidden password field. + EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled()); + + // Now make the input visible. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setAttribute("style", "", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled()); +} + +// Tests that a Mojo message is sent when a previously non-password field +// becomes a password. +TEST(PasswordInputTypeTest, NonPasswordFieldBecomesPassword) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='text'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + // The message should not be sent for a non-password field. + blink::test::RunPendingTasks(); + EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled()); + + // Now make the input a password field. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setType("password"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled()); +} + +// Tests that a Mojo message is *not* sent when a previously invisible password +// field becomes a visible non-password field. +TEST(PasswordInputTypeTest, + InvisiblePasswordFieldBecomesVisibleNonPasswordField) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password' style='display:none;'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + // The message should not be sent for a hidden password field. + EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled()); + + // Now make the input a visible non-password field. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setType("text"); + input->setAttribute("style", "", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled()); +} + +// Tests that a Mojo message is sent when the only visible password +// field becomes invisible. +TEST(PasswordInputTypeTest, VisiblePasswordFieldBecomesInvisible) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_TRUE(mock_service.PasswordFieldVisibleCalled()); + EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // Now make the input invisible. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); +} + +// Tests that a Mojo message is sent when all visible password fields +// become invisible. +TEST(PasswordInputTypeTest, AllVisiblePasswordFieldBecomeInvisible) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'><input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // Make the first input invisible. There should be no message because + // there is still a visible input. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // When all inputs are invisible, then a message should be sent. + input = ToHTMLInputElement(page_holder->GetDocument().body()->lastChild()); + input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // If the count of visible inputs goes positive again and then back to + // zero, a message should be sent again. + input->setAttribute("style", "", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); + input->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(2u, mock_service.NumPasswordFieldsInvisibleCalls()); +} + +// Tests that a Mojo message is sent when the containing element of a +// visible password field becomes invisible. +TEST(PasswordInputTypeTest, PasswordFieldContainerBecomesInvisible) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<div><input type='password'></div>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // If the containing div becomes invisible, a message should be sent. + HTMLElement* div = + ToHTMLDivElement(page_holder->GetDocument().body()->firstChild()); + div->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // If the containing div becomes visible and then invisible again, a message + // should be sent. + div->setAttribute("style", "", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); + div->setAttribute("style", "display:none;", ASSERT_NO_EXCEPTION); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(2u, mock_service.NumPasswordFieldsInvisibleCalls()); +} + +// Tests that a Mojo message is sent when all visible password fields +// become non-password fields. +TEST(PasswordInputTypeTest, PasswordFieldsBecomeNonPasswordFields) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'><input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // Make the first input a non-password input. There should be no + // message because there is still a visible password input. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setType("text"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.NumPasswordFieldsInvisibleCalls()); + + // When all inputs are no longer passwords, then a message should be sent. + input = ToHTMLInputElement(page_holder->GetDocument().body()->lastChild()); + input->setType("text"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); +} + +// Tests that only one Mojo message is sent for multiple password +// visibility events within the same task. +TEST(PasswordInputTypeTest, MultipleEventsInSameTask) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + // Make the password field invisible in the same task. + HTMLInputElement* input = + ToHTMLInputElement(page_holder->GetDocument().body()->firstChild()); + input->setType("text"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + // Only a single Mojo message should have been sent, with the latest state of + // the page (which is that no password fields are visible). + EXPECT_EQ(1u, mock_service.NumPasswordFieldsInvisibleCalls()); + EXPECT_FALSE(mock_service.PasswordFieldVisibleCalled()); +} + +// Tests that a Mojo message is sent when a password field is edited +// on the page. +TEST(PasswordInputTypeTest, DidEditFieldEvent) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.DidEditFieldCalls()); + // Simulate a text field edit. + page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.DidEditFieldCalls()); + // Ensure additional edits do not trigger additional notifications. + page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext(); + blink::test::RunPendingTasks(); + EXPECT_EQ(1u, mock_service.DidEditFieldCalls()); +} + +// Tests that a Mojo message is not sent when a password field is edited +// in a secure context. +TEST(PasswordInputTypeTest, DidEditFieldEventNotSentFromSecureContext) { + std::unique_ptr<DummyPageHolder> page_holder = + DummyPageHolder::Create(IntSize(2000, 2000), nullptr, nullptr, nullptr); + MockInsecureInputService mock_service(page_holder->GetFrame()); + page_holder->GetDocument().SetURL(KURL("https://example.test")); + page_holder->GetDocument().SetSecurityOrigin( + SecurityOrigin::Create(KURL("https://example.test"))); + page_holder->GetDocument().SetSecureContextStateForTesting( + SecureContextState::kSecure); + page_holder->GetDocument().body()->SetInnerHTMLFromString( + "<input type='password'>"); + page_holder->GetDocument().View()->UpdateAllLifecyclePhases(); + // Simulate a text field edit. + page_holder->GetDocument().MaybeQueueSendDidEditFieldInInsecureContext(); + // No message should have been sent from a secure context. + blink::test::RunPendingTasks(); + EXPECT_EQ(0u, mock_service.DidEditFieldCalls()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc new file mode 100644 index 00000000000..38bc9b09567 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/picker_indicator_element.h" + +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/frame/settings.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/layout/layout_details_marker.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/layout_test_support.h" + +namespace blink { + +using namespace HTMLNames; + +inline PickerIndicatorElement::PickerIndicatorElement( + Document& document, + PickerIndicatorOwner& picker_indicator_owner) + : HTMLDivElement(document), + picker_indicator_owner_(&picker_indicator_owner) {} + +PickerIndicatorElement* PickerIndicatorElement::Create( + Document& document, + PickerIndicatorOwner& picker_indicator_owner) { + PickerIndicatorElement* element = + new PickerIndicatorElement(document, picker_indicator_owner); + element->SetShadowPseudoId(AtomicString("-webkit-calendar-picker-indicator")); + element->setAttribute(idAttr, ShadowElementNames::PickerIndicator()); + return element; +} + +PickerIndicatorElement::~PickerIndicatorElement() { + DCHECK(!chooser_); +} + +LayoutObject* PickerIndicatorElement::CreateLayoutObject(const ComputedStyle&) { + return new LayoutDetailsMarker(this); +} + +void PickerIndicatorElement::DefaultEventHandler(Event* event) { + if (!GetLayoutObject()) + return; + if (!picker_indicator_owner_ || + picker_indicator_owner_->IsPickerIndicatorOwnerDisabledOrReadOnly()) + return; + + if (event->type() == EventTypeNames::click) { + OpenPopup(); + event->SetDefaultHandled(); + } else if (event->type() == EventTypeNames::keypress && + event->IsKeyboardEvent()) { + int char_code = ToKeyboardEvent(event)->charCode(); + if (char_code == ' ' || char_code == '\r') { + OpenPopup(); + event->SetDefaultHandled(); + } + } + + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); +} + +bool PickerIndicatorElement::WillRespondToMouseClickEvents() { + if (GetLayoutObject() && picker_indicator_owner_ && + !picker_indicator_owner_->IsPickerIndicatorOwnerDisabledOrReadOnly()) + return true; + + return HTMLDivElement::WillRespondToMouseClickEvents(); +} + +void PickerIndicatorElement::DidChooseValue(const String& value) { + if (!picker_indicator_owner_) + return; + picker_indicator_owner_->PickerIndicatorChooseValue(value); +} + +void PickerIndicatorElement::DidChooseValue(double value) { + if (picker_indicator_owner_) + picker_indicator_owner_->PickerIndicatorChooseValue(value); +} + +void PickerIndicatorElement::DidEndChooser() { + chooser_.Clear(); +} + +void PickerIndicatorElement::OpenPopup() { + if (chooser_) + return; + if (!GetDocument().GetPage()) + return; + if (!picker_indicator_owner_) + return; + DateTimeChooserParameters parameters; + if (!picker_indicator_owner_->SetupDateTimeChooserParameters(parameters)) + return; + chooser_ = GetDocument().GetPage()->GetChromeClient().OpenDateTimeChooser( + this, parameters); +} + +Element& PickerIndicatorElement::OwnerElement() const { + DCHECK(picker_indicator_owner_); + return picker_indicator_owner_->PickerOwnerElement(); +} + +void PickerIndicatorElement::ClosePopup() { + if (!chooser_) + return; + chooser_->EndChooser(); +} + +void PickerIndicatorElement::DetachLayoutTree(const AttachContext& context) { + ClosePopup(); + HTMLDivElement::DetachLayoutTree(context); +} + +AXObject* PickerIndicatorElement::PopupRootAXObject() const { + return chooser_ ? chooser_->RootAXObject() : nullptr; +} + +bool PickerIndicatorElement::IsPickerIndicatorElement() const { + return true; +} + +Node::InsertionNotificationRequest PickerIndicatorElement::InsertedInto( + ContainerNode* insertion_point) { + HTMLDivElement::InsertedInto(insertion_point); + return kInsertionShouldCallDidNotifySubtreeInsertions; +} + +void PickerIndicatorElement::DidNotifySubtreeInsertionsToDocument() { + if (!GetDocument().GetSettings() || + !GetDocument().GetSettings()->GetAccessibilityEnabled()) + return; + // Don't make this focusable if we are in layout tests in order to avoid to + // break existing tests. + // FIXME: We should have a way to disable accessibility in layout tests. + if (LayoutTestSupport::IsRunningLayoutTest()) + return; + setAttribute(tabindexAttr, "0"); + setAttribute(aria_haspopupAttr, "true"); + setAttribute(roleAttr, "button"); +} + +void PickerIndicatorElement::Trace(blink::Visitor* visitor) { + visitor->Trace(picker_indicator_owner_); + visitor->Trace(chooser_); + HTMLDivElement::Trace(visitor); + DateTimeChooserClient::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h new file mode 100644 index 00000000000..c0c6fe30e6f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/picker_indicator_element.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PICKER_INDICATOR_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_PICKER_INDICATOR_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/forms/date_time_chooser.h" +#include "third_party/blink/renderer/core/html/forms/date_time_chooser_client.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" + +namespace blink { + +class HTMLInputElement; + +class PickerIndicatorElement final : public HTMLDivElement, + public DateTimeChooserClient { + USING_GARBAGE_COLLECTED_MIXIN(PickerIndicatorElement); + + public: + // PickerIndicatorOwner implementer must call removePickerIndicatorOwner when + // it doesn't handle event, e.g. at destruction. + class PickerIndicatorOwner : public GarbageCollectedMixin { + public: + virtual ~PickerIndicatorOwner() = default; + virtual bool IsPickerIndicatorOwnerDisabledOrReadOnly() const = 0; + // FIXME: Remove. Deprecated in favor of double version. + virtual void PickerIndicatorChooseValue(const String&) = 0; + virtual void PickerIndicatorChooseValue(double) = 0; + virtual Element& PickerOwnerElement() const = 0; + virtual bool SetupDateTimeChooserParameters(DateTimeChooserParameters&) = 0; + }; + + static PickerIndicatorElement* Create(Document&, PickerIndicatorOwner&); + ~PickerIndicatorElement() override; + void Trace(blink::Visitor*) override; + + void OpenPopup(); + void ClosePopup(); + bool WillRespondToMouseClickEvents() override; + void RemovePickerIndicatorOwner() { picker_indicator_owner_ = nullptr; } + AXObject* PopupRootAXObject() const; + + // DateTimeChooserClient implementation. + Element& OwnerElement() const override; + void DidChooseValue(const String&) override; + void DidChooseValue(double) override; + void DidEndChooser() override; + + private: + PickerIndicatorElement(Document&, PickerIndicatorOwner&); + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + void DefaultEventHandler(Event*) override; + void DetachLayoutTree(const AttachContext& = AttachContext()) override; + bool IsPickerIndicatorElement() const override; + InsertionNotificationRequest InsertedInto(ContainerNode*) override; + void DidNotifySubtreeInsertionsToDocument() override; + + HTMLInputElement* HostInput(); + + Member<PickerIndicatorOwner> picker_indicator_owner_; + Member<DateTimeChooser> chooser_; +}; + +DEFINE_TYPE_CASTS(PickerIndicatorElement, + Element, + element, + element->IsPickerIndicatorElement(), + element.IsPickerIndicatorElement()); + +} // namespace blink +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/popup_menu.h b/chromium/third_party/blink/renderer/core/html/forms/popup_menu.h new file mode 100644 index 00000000000..c2175a18ada --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/popup_menu.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_POPUP_MENU_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_POPUP_MENU_H_ + +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/platform_export.h" + +namespace blink { + +class PopupMenu : public GarbageCollectedFinalized<PopupMenu> { + public: + virtual ~PopupMenu() = default; + virtual void Trace(blink::Visitor* visitor) {} + virtual void Show() = 0; + virtual void Hide() = 0; + enum UpdateReason { + kBySelectionChange, + kByStyleChange, + kByDOMChange, + }; + virtual void UpdateFromElement(UpdateReason) = 0; + virtual void DisconnectClient() = 0; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_POPUP_MENU_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc new file mode 100644 index 00000000000..9f997f23b71 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.cc @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2007, 2008, 2009 Apple Inc. 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 "third_party/blink/renderer/core/html/forms/radio_button_group_scope.h" + +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" + +namespace blink { + +class RadioButtonGroup : public GarbageCollected<RadioButtonGroup> { + public: + static RadioButtonGroup* Create(); + bool IsEmpty() const { return members_.IsEmpty(); } + bool IsRequired() const { return required_count_; } + HTMLInputElement* CheckedButton() const { return checked_button_; } + void Add(HTMLInputElement*); + void UpdateCheckedState(HTMLInputElement*); + void RequiredAttributeChanged(HTMLInputElement*); + void Remove(HTMLInputElement*); + bool Contains(HTMLInputElement*) const; + unsigned size() const; + + void Trace(blink::Visitor*); + + private: + RadioButtonGroup(); + void SetNeedsValidityCheckForAllButtons(); + bool IsValid() const; + void SetCheckedButton(HTMLInputElement*); + + // The map records the 'required' state of each (button) element. + using Members = HeapHashMap<Member<HTMLInputElement>, bool>; + + using MemberKeyValue = WTF::KeyValuePair<Member<HTMLInputElement>, bool>; + + void UpdateRequiredButton(MemberKeyValue&, bool is_required); + + Members members_; + Member<HTMLInputElement> checked_button_; + size_t required_count_; +}; + +RadioButtonGroup::RadioButtonGroup() + : checked_button_(nullptr), required_count_(0) {} + +RadioButtonGroup* RadioButtonGroup::Create() { + return new RadioButtonGroup; +} + +inline bool RadioButtonGroup::IsValid() const { + return !IsRequired() || checked_button_; +} + +void RadioButtonGroup::SetCheckedButton(HTMLInputElement* button) { + HTMLInputElement* old_checked_button = checked_button_; + if (old_checked_button == button) + return; + checked_button_ = button; + if (old_checked_button) + old_checked_button->setChecked(false); +} + +void RadioButtonGroup::UpdateRequiredButton(MemberKeyValue& it, + bool is_required) { + if (it.value == is_required) + return; + + it.value = is_required; + if (is_required) { + required_count_++; + } else { + DCHECK_GT(required_count_, 0u); + required_count_--; + } +} + +void RadioButtonGroup::Add(HTMLInputElement* button) { + DCHECK_EQ(button->type(), InputTypeNames::radio); + auto add_result = members_.insert(button, false); + if (!add_result.is_new_entry) + return; + bool group_was_valid = IsValid(); + UpdateRequiredButton(*add_result.stored_value, button->IsRequired()); + if (button->checked()) + SetCheckedButton(button); + + bool group_is_valid = IsValid(); + if (group_was_valid != group_is_valid) { + SetNeedsValidityCheckForAllButtons(); + } else if (!group_is_valid) { + // A radio button not in a group is always valid. We need to make it + // invalid only if the group is invalid. + button->SetNeedsValidityCheck(); + } +} + +void RadioButtonGroup::UpdateCheckedState(HTMLInputElement* button) { + DCHECK_EQ(button->type(), InputTypeNames::radio); + DCHECK(members_.Contains(button)); + bool was_valid = IsValid(); + if (button->checked()) { + SetCheckedButton(button); + } else { + if (checked_button_ == button) + checked_button_ = nullptr; + } + if (was_valid != IsValid()) + SetNeedsValidityCheckForAllButtons(); + for (auto& member : members_) { + HTMLInputElement* const input_element = member.key; + input_element->PseudoStateChanged(CSSSelector::kPseudoIndeterminate); + } +} + +void RadioButtonGroup::RequiredAttributeChanged(HTMLInputElement* button) { + DCHECK_EQ(button->type(), InputTypeNames::radio); + auto it = members_.find(button); + DCHECK_NE(it, members_.end()); + bool was_valid = IsValid(); + // Synchronize the 'required' flag for the button, along with + // updating the overall count. + UpdateRequiredButton(*it, button->IsRequired()); + if (was_valid != IsValid()) + SetNeedsValidityCheckForAllButtons(); +} + +void RadioButtonGroup::Remove(HTMLInputElement* button) { + DCHECK_EQ(button->type(), InputTypeNames::radio); + auto it = members_.find(button); + if (it == members_.end()) + return; + bool was_valid = IsValid(); + DCHECK_EQ(it->value, button->IsRequired()); + UpdateRequiredButton(*it, false); + members_.erase(it); + if (checked_button_ == button) + checked_button_ = nullptr; + + if (members_.IsEmpty()) { + DCHECK(!required_count_); + DCHECK(!checked_button_); + } else if (was_valid != IsValid()) { + SetNeedsValidityCheckForAllButtons(); + } + if (!was_valid) { + // A radio button not in a group is always valid. We need to make it + // valid only if the group was invalid. + button->SetNeedsValidityCheck(); + } + + // Send notification to update AX attributes for AXObjects which radiobutton + // group has. + if (!members_.IsEmpty()) { + HTMLInputElement* input = members_.begin()->key; + if (AXObjectCache* cache = input->GetDocument().ExistingAXObjectCache()) + cache->RadiobuttonRemovedFromGroup(input); + } +} + +void RadioButtonGroup::SetNeedsValidityCheckForAllButtons() { + for (auto& element : members_) { + HTMLInputElement* const button = element.key; + DCHECK_EQ(button->type(), InputTypeNames::radio); + button->SetNeedsValidityCheck(); + } +} + +bool RadioButtonGroup::Contains(HTMLInputElement* button) const { + return members_.Contains(button); +} + +unsigned RadioButtonGroup::size() const { + return members_.size(); +} + +void RadioButtonGroup::Trace(blink::Visitor* visitor) { + visitor->Trace(members_); + visitor->Trace(checked_button_); +} + +// ---------------------------------------------------------------- + +// Explicity define empty constructor and destructor in order to prevent the +// compiler from generating them as inlines. So we don't need to to define +// RadioButtonGroup in the header. +RadioButtonGroupScope::RadioButtonGroupScope() = default; + +RadioButtonGroupScope::~RadioButtonGroupScope() = default; + +void RadioButtonGroupScope::AddButton(HTMLInputElement* element) { + DCHECK_EQ(element->type(), InputTypeNames::radio); + if (element->GetName().IsEmpty()) + return; + + if (!name_to_group_map_) + name_to_group_map_ = new NameToGroupMap; + + auto* key_value = + name_to_group_map_->insert(element->GetName(), nullptr).stored_value; + if (!key_value->value) + key_value->value = RadioButtonGroup::Create(); + key_value->value->Add(element); +} + +void RadioButtonGroupScope::UpdateCheckedState(HTMLInputElement* element) { + DCHECK_EQ(element->type(), InputTypeNames::radio); + if (element->GetName().IsEmpty()) + return; + DCHECK(name_to_group_map_); + if (!name_to_group_map_) + return; + RadioButtonGroup* group = name_to_group_map_->at(element->GetName()); + DCHECK(group); + group->UpdateCheckedState(element); +} + +void RadioButtonGroupScope::RequiredAttributeChanged( + HTMLInputElement* element) { + DCHECK_EQ(element->type(), InputTypeNames::radio); + if (element->GetName().IsEmpty()) + return; + DCHECK(name_to_group_map_); + if (!name_to_group_map_) + return; + RadioButtonGroup* group = name_to_group_map_->at(element->GetName()); + DCHECK(group); + group->RequiredAttributeChanged(element); +} + +HTMLInputElement* RadioButtonGroupScope::CheckedButtonForGroup( + const AtomicString& name) const { + if (!name_to_group_map_) + return nullptr; + RadioButtonGroup* group = name_to_group_map_->at(name); + return group ? group->CheckedButton() : nullptr; +} + +bool RadioButtonGroupScope::IsInRequiredGroup(HTMLInputElement* element) const { + DCHECK_EQ(element->type(), InputTypeNames::radio); + if (element->GetName().IsEmpty()) + return false; + if (!name_to_group_map_) + return false; + RadioButtonGroup* group = name_to_group_map_->at(element->GetName()); + return group && group->IsRequired() && group->Contains(element); +} + +unsigned RadioButtonGroupScope::GroupSizeFor( + const HTMLInputElement* element) const { + if (!name_to_group_map_) + return 0; + + RadioButtonGroup* group = name_to_group_map_->at(element->GetName()); + if (!group) + return 0; + return group->size(); +} + +void RadioButtonGroupScope::RemoveButton(HTMLInputElement* element) { + DCHECK_EQ(element->type(), InputTypeNames::radio); + if (element->GetName().IsEmpty()) + return; + if (!name_to_group_map_) + return; + + RadioButtonGroup* group = name_to_group_map_->at(element->GetName()); + if (!group) + return; + group->Remove(element); + if (group->IsEmpty()) { + // We don't remove an empty RadioButtonGroup from m_nameToGroupMap for + // better performance. + DCHECK(!group->IsRequired()); + SECURITY_DCHECK(!group->CheckedButton()); + } +} + +void RadioButtonGroupScope::Trace(blink::Visitor* visitor) { + visitor->Trace(name_to_group_map_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h new file mode 100644 index 00000000000..8c8c9a20a87 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_button_group_scope.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007, 2008, 2009 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_BUTTON_GROUP_SCOPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_BUTTON_GROUP_SCOPE_H_ + +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" + +namespace blink { + +class HTMLInputElement; +class RadioButtonGroup; + +class RadioButtonGroupScope { + DISALLOW_NEW(); + + public: + RadioButtonGroupScope(); + ~RadioButtonGroupScope(); + void Trace(blink::Visitor*); + void AddButton(HTMLInputElement*); + void UpdateCheckedState(HTMLInputElement*); + void RequiredAttributeChanged(HTMLInputElement*); + void RemoveButton(HTMLInputElement*); + HTMLInputElement* CheckedButtonForGroup(const AtomicString& group_name) const; + bool IsInRequiredGroup(HTMLInputElement*) const; + unsigned GroupSizeFor(const HTMLInputElement*) const; + + private: + using NameToGroupMap = HeapHashMap<AtomicString, Member<RadioButtonGroup>>; + Member<NameToGroupMap> name_to_group_map_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_BUTTON_GROUP_SCOPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc new file mode 100644 index 00000000000..0ca386d492d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.cc @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2005, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. 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 "third_party/blink/renderer/core/html/forms/radio_input_type.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/page/spatial_navigation.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +namespace { + +HTMLInputElement* NextInputElement(const HTMLInputElement& element, + const HTMLFormElement* stay_within, + bool forward) { + return forward ? Traversal<HTMLInputElement>::Next(element, stay_within) + : Traversal<HTMLInputElement>::Previous(element, stay_within); +} + +} // namespace + +using namespace HTMLNames; + +InputType* RadioInputType::Create(HTMLInputElement& element) { + return new RadioInputType(element); +} + +const AtomicString& RadioInputType::FormControlType() const { + return InputTypeNames::radio; +} + +bool RadioInputType::ValueMissing(const String&) const { + return GetElement().IsInRequiredRadioButtonGroup() && + !GetElement().CheckedRadioButtonForGroup(); +} + +String RadioInputType::ValueMissingText() const { + return GetLocale().QueryString( + WebLocalizedString::kValidationValueMissingForRadio); +} + +void RadioInputType::HandleClickEvent(MouseEvent* event) { + event->SetDefaultHandled(); +} + +HTMLInputElement* RadioInputType::FindNextFocusableRadioButtonInGroup( + HTMLInputElement* current_element, + bool forward) { + for (HTMLInputElement* input_element = + NextRadioButtonInGroup(current_element, forward); + input_element; + input_element = NextRadioButtonInGroup(input_element, forward)) { + if (input_element->IsFocusable()) + return input_element; + } + return nullptr; +} + +void RadioInputType::HandleKeydownEvent(KeyboardEvent* event) { + // TODO(tkent): We should return more earlier. + if (!GetElement().GetLayoutObject()) + return; + BaseCheckableInputType::HandleKeydownEvent(event); + if (event->DefaultHandled()) + return; + const String& key = event->key(); + if (key != "ArrowUp" && key != "ArrowDown" && key != "ArrowLeft" && + key != "ArrowRight") + return; + + if (event->ctrlKey() || event->metaKey() || event->altKey()) + return; + + // Left and up mean "previous radio button". + // Right and down mean "next radio button". + // Tested in WinIE, and even for RTL, left still means previous radio button + // (and so moves to the right). Seems strange, but we'll match it. However, + // when using Spatial Navigation, we need to be able to navigate without + // changing the selection. + Document& document = GetElement().GetDocument(); + if (IsSpatialNavigationEnabled(document.GetFrame())) + return; + bool forward = ComputedTextDirection() == TextDirection::kRtl + ? (key == "ArrowDown" || key == "ArrowLeft") + : (key == "ArrowDown" || key == "ArrowRight"); + + // Force layout for isFocusable() in findNextFocusableRadioButtonInGroup(). + document.UpdateStyleAndLayoutIgnorePendingStylesheets(); + + // We can only stay within the form's children if the form hasn't been demoted + // to a leaf because of malformed HTML. + HTMLInputElement* input_element = + FindNextFocusableRadioButtonInGroup(&GetElement(), forward); + if (!input_element) { + // Traverse in reverse direction till last or first radio button + forward = !(forward); + HTMLInputElement* next_input_element = + FindNextFocusableRadioButtonInGroup(&GetElement(), forward); + while (next_input_element) { + input_element = next_input_element; + next_input_element = + FindNextFocusableRadioButtonInGroup(next_input_element, forward); + } + } + if (input_element) { + document.SetFocusedElement(input_element, + FocusParams(SelectionBehaviorOnFocus::kRestore, + kWebFocusTypeNone, nullptr)); + input_element->DispatchSimulatedClick(event, kSendNoEvents); + event->SetDefaultHandled(); + return; + } +} + +void RadioInputType::HandleKeyupEvent(KeyboardEvent* event) { + const String& key = event->key(); + if (key != " ") + return; + // If an unselected radio is tabbed into (because the entire group has nothing + // checked, or because of some explicit .focus() call), then allow space to + // check it. + if (GetElement().checked()) + return; + DispatchSimulatedClickIfActive(event); +} + +bool RadioInputType::IsKeyboardFocusable() const { + if (!InputType::IsKeyboardFocusable()) + return false; + + // When using Spatial Navigation, every radio button should be focusable. + if (IsSpatialNavigationEnabled(GetElement().GetDocument().GetFrame())) + return true; + + // Never allow keyboard tabbing to leave you in the same radio group. Always + // skip any other elements in the group. + Element* current_focused_element = + GetElement().GetDocument().FocusedElement(); + if (auto* focused_input = ToHTMLInputElementOrNull(current_focused_element)) { + if (focused_input->type() == InputTypeNames::radio && + focused_input->Form() == GetElement().Form() && + focused_input->GetName() == GetElement().GetName()) + return false; + } + + // Allow keyboard focus if we're checked or if nothing in the group is + // checked. + return GetElement().checked() || !GetElement().CheckedRadioButtonForGroup(); +} + +bool RadioInputType::ShouldSendChangeEventAfterCheckedChanged() { + // Don't send a change event for a radio button that's getting unchecked. + // This was done to match the behavior of other browsers. + return GetElement().checked(); +} + +ClickHandlingState* RadioInputType::WillDispatchClick() { + // An event handler can use preventDefault or "return false" to reverse the + // selection we do here. The ClickHandlingState object contains what we need + // to undo what we did here in didDispatchClick. + + // We want radio groups to end up in sane states, i.e., to have something + // checked. Therefore if nothing is currently selected, we won't allow the + // upcoming action to be "undone", since we want some object in the radio + // group to actually get selected. + + ClickHandlingState* state = new ClickHandlingState; + + state->checked = GetElement().checked(); + state->checked_radio_button = GetElement().CheckedRadioButtonForGroup(); + GetElement().setChecked(true, kDispatchChangeEvent); + is_in_click_handler_ = true; + return state; +} + +void RadioInputType::DidDispatchClick(Event* event, + const ClickHandlingState& state) { + if (event->defaultPrevented() || event->DefaultHandled()) { + // Restore the original selected radio button if possible. + // Make sure it is still a radio button and only do the restoration if it + // still belongs to our group. + HTMLInputElement* checked_radio_button = state.checked_radio_button.Get(); + if (!checked_radio_button) + GetElement().setChecked(false); + else if (checked_radio_button->type() == InputTypeNames::radio && + checked_radio_button->Form() == GetElement().Form() && + checked_radio_button->GetName() == GetElement().GetName()) + checked_radio_button->setChecked(true); + } else if (state.checked != GetElement().checked()) { + GetElement().DispatchInputAndChangeEventIfNeeded(); + } + is_in_click_handler_ = false; + // The work we did in willDispatchClick was default handling. + event->SetDefaultHandled(); +} + +bool RadioInputType::ShouldAppearIndeterminate() const { + return !GetElement().CheckedRadioButtonForGroup(); +} + +HTMLInputElement* RadioInputType::NextRadioButtonInGroup( + HTMLInputElement* current, + bool forward) { + // TODO(tkent): Staying within form() is incorrect. This code ignore input + // elements associated by |form| content attribute. + // TODO(tkent): Comparing name() with == is incorrect. It should be + // case-insensitive. + for (HTMLInputElement* input_element = + NextInputElement(*current, current->Form(), forward); + input_element; input_element = NextInputElement( + *input_element, current->Form(), forward)) { + if (current->Form() == input_element->Form() && + input_element->type() == InputTypeNames::radio && + input_element->GetName() == current->GetName()) + return input_element; + } + return nullptr; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h new file mode 100644 index 00000000000..7c1740d85da --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_input_type.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/forms/base_checkable_input_type.h" + +namespace blink { + +class RadioInputType final : public BaseCheckableInputType { + public: + static InputType* Create(HTMLInputElement&); + CORE_EXPORT static HTMLInputElement* NextRadioButtonInGroup(HTMLInputElement*, + bool forward); + + private: + RadioInputType(HTMLInputElement& element) : BaseCheckableInputType(element) {} + const AtomicString& FormControlType() const override; + bool ValueMissing(const String&) const override; + String ValueMissingText() const override; + void HandleClickEvent(MouseEvent*) override; + void HandleKeydownEvent(KeyboardEvent*) override; + void HandleKeyupEvent(KeyboardEvent*) override; + bool IsKeyboardFocusable() const override; + bool ShouldSendChangeEventAfterCheckedChanged() override; + ClickHandlingState* WillDispatchClick() override; + void DidDispatchClick(Event*, const ClickHandlingState&) override; + bool ShouldAppearIndeterminate() const override; + + HTMLInputElement* FindNextFocusableRadioButtonInGroup(HTMLInputElement*, + bool); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc new file mode 100644 index 00000000000..7170f0e8803 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.cc @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2012 Motorola Mobility, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY MOTOROLA MOBILITY, INC. AND ITS CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY, INC. OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/radio_node_list.h" + +#include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/dom/node_rare_data.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/html_image_element.h" +#include "third_party/blink/renderer/core/html/html_object_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" + +namespace blink { + +using namespace HTMLNames; + +RadioNodeList::RadioNodeList(ContainerNode& root_node, + const AtomicString& name, + CollectionType type) + : LiveNodeList(root_node, + type, + kInvalidateForFormControls, + IsHTMLFormElement(root_node) + ? NodeListSearchRoot::kTreeScope + : NodeListSearchRoot::kOwnerNode), + name_(name) {} + +RadioNodeList::~RadioNodeList() = default; + +static inline HTMLInputElement* ToRadioButtonInputElement(Element& element) { + if (!IsHTMLInputElement(element)) + return nullptr; + HTMLInputElement& input_element = ToHTMLInputElement(element); + if (input_element.type() != InputTypeNames::radio || + input_element.value().IsEmpty()) + return nullptr; + return &input_element; +} + +String RadioNodeList::value() const { + if (ShouldOnlyMatchImgElements()) + return String(); + unsigned length = this->length(); + for (unsigned i = 0; i < length; ++i) { + const HTMLInputElement* input_element = ToRadioButtonInputElement(*item(i)); + if (!input_element || !input_element->checked()) + continue; + return input_element->value(); + } + return String(); +} + +void RadioNodeList::setValue(const String& value) { + if (ShouldOnlyMatchImgElements()) + return; + unsigned length = this->length(); + for (unsigned i = 0; i < length; ++i) { + HTMLInputElement* input_element = ToRadioButtonInputElement(*item(i)); + if (!input_element || input_element->value() != value) + continue; + input_element->setChecked(true); + return; + } +} + +bool RadioNodeList::MatchesByIdOrName(const Element& test_element) const { + return test_element.GetIdAttribute() == name_ || + test_element.GetNameAttribute() == name_; +} + +bool RadioNodeList::CheckElementMatchesRadioNodeListFilter( + const Element& test_element) const { + DCHECK(!ShouldOnlyMatchImgElements()); + DCHECK(IsHTMLObjectElement(test_element) || + test_element.IsFormControlElement()); + if (IsHTMLFormElement(ownerNode())) { + HTMLFormElement* form_element = ToHTMLElement(test_element).formOwner(); + if (!form_element || form_element != ownerNode()) + return false; + } + + return MatchesByIdOrName(test_element); +} + +bool RadioNodeList::ElementMatches(const Element& element) const { + if (ShouldOnlyMatchImgElements()) { + if (!IsHTMLImageElement(element)) + return false; + + if (ToHTMLImageElement(element).formOwner() != ownerNode()) + return false; + + return MatchesByIdOrName(element); + } + + if (!IsHTMLObjectElement(element) && !element.IsFormControlElement()) + return false; + + if (IsHTMLInputElement(element) && + ToHTMLInputElement(element).type() == InputTypeNames::image) + return false; + + return CheckElementMatchesRadioNodeListFilter(element); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h new file mode 100644 index 00000000000..81e76d5601c --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2012 Motorola Mobility, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY MOTOROLA MOBILITY, INC. AND ITS CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY, INC. OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_NODE_LIST_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_NODE_LIST_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/core/dom/live_node_list.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" + +namespace blink { + +class RadioNodeList final : public LiveNodeList { + DEFINE_WRAPPERTYPEINFO(); + + public: + static RadioNodeList* Create(ContainerNode& owner_node, + CollectionType type, + const AtomicString& name) { + DCHECK(type == kRadioNodeListType || type == kRadioImgNodeListType); + return new RadioNodeList(owner_node, name, type); + } + + ~RadioNodeList() override; + + String value() const; + void setValue(const String&); + + private: + RadioNodeList(ContainerNode&, const AtomicString& name, CollectionType); + + bool CheckElementMatchesRadioNodeListFilter(const Element&) const; + + bool MatchesByIdOrName(const Element&) const; + bool ShouldOnlyMatchImgElements() const { + return GetType() == kRadioImgNodeListType; + } + + bool ElementMatches(const Element&) const override; + + AtomicString name_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RADIO_NODE_LIST_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl new file mode 100644 index 00000000000..3f7fde99fdd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/radio_node_list.idl @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012 Motorola Mobility, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY MOTOROLA MOBILITY, INC. AND ITS CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY, INC. OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +// https://html.spec.whatwg.org/#radionodelist + +[ + Exposed=Window +] interface RadioNodeList : NodeList { + attribute DOMString value; + + // TODO(tkent): We need to declare this indexed property getter because our + // IDL compiler doesn't support inheritance of indexed property + // getters. crbug.com/752877 + [ImplementedAs=item] getter Node? (unsigned long index); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc new file mode 100644 index 00000000000..771acd0215a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.cc @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/range_input_type.h" + +#include <algorithm> +#include <limits> +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" +#include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" +#include "third_party/blink/renderer/core/html/forms/slider_thumb_element.h" +#include "third_party/blink/renderer/core/html/forms/step_range.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_slider.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" + +namespace blink { + +using namespace HTMLNames; + +static const int kRangeDefaultMinimum = 0; +static const int kRangeDefaultMaximum = 100; +static const int kRangeDefaultStep = 1; +static const int kRangeDefaultStepBase = 0; +static const int kRangeStepScaleFactor = 1; + +static Decimal EnsureMaximum(const Decimal& proposed_value, + const Decimal& minimum) { + return proposed_value >= minimum ? proposed_value : minimum; +} + +InputType* RangeInputType::Create(HTMLInputElement& element) { + return new RangeInputType(element); +} + +RangeInputType::RangeInputType(HTMLInputElement& element) + : InputType(element), + InputTypeView(element), + tick_mark_values_dirty_(true) {} + +void RangeInputType::Trace(blink::Visitor* visitor) { + InputTypeView::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* RangeInputType::CreateView() { + return this; +} + +InputType::ValueMode RangeInputType::GetValueMode() const { + return ValueMode::kValue; +} + +void RangeInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeRange); + if (const ComputedStyle* style = GetElement().GetComputedStyle()) { + if (style->Appearance() == kSliderVerticalPart) { + UseCounter::Count(GetElement().GetDocument(), + WebFeature::kInputTypeRangeVerticalAppearance); + } + } +} + +const AtomicString& RangeInputType::FormControlType() const { + return InputTypeNames::range; +} + +double RangeInputType::ValueAsDouble() const { + return ParseToDoubleForNumberType(GetElement().value()); +} + +void RangeInputType::SetValueAsDouble(double new_value, + TextFieldEventBehavior event_behavior, + ExceptionState& exception_state) const { + SetValueAsDecimal(Decimal::FromDouble(new_value), event_behavior, + exception_state); +} + +bool RangeInputType::TypeMismatchFor(const String& value) const { + return !value.IsEmpty() && !std::isfinite(ParseToDoubleForNumberType(value)); +} + +bool RangeInputType::SupportsRequired() const { + return false; +} + +StepRange RangeInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL( + const StepRange::StepDescription, step_description, + (kRangeDefaultStep, kRangeDefaultStepBase, kRangeStepScaleFactor)); + + const Decimal step_base = FindStepBase(kRangeDefaultStepBase); + const Decimal minimum = ParseToNumber(GetElement().FastGetAttribute(minAttr), + kRangeDefaultMinimum); + const Decimal maximum = + EnsureMaximum(ParseToNumber(GetElement().FastGetAttribute(maxAttr), + kRangeDefaultMaximum), + minimum); + + const Decimal step = + StepRange::ParseStep(any_step_handling, step_description, + GetElement().FastGetAttribute(stepAttr)); + // Range type always has range limitations because it has default + // minimum/maximum. + // https://html.spec.whatwg.org/multipage/forms.html#range-state-(type=range):concept-input-min-default + const bool kHasRangeLimitations = true; + return StepRange(step_base, minimum, maximum, kHasRangeLimitations, step, + step_description); +} + +bool RangeInputType::IsSteppable() const { + return true; +} + +void RangeInputType::HandleMouseDownEvent(MouseEvent* event) { + if (GetElement().IsDisabledFormControl()) + return; + + Node* target_node = event->target()->ToNode(); + if (event->button() != + static_cast<short>(WebPointerProperties::Button::kLeft) || + !target_node) + return; + DCHECK(IsShadowHost(GetElement())); + if (target_node != GetElement() && + !target_node->IsDescendantOf(GetElement().UserAgentShadowRoot())) + return; + SliderThumbElement* thumb = GetSliderThumbElement(); + if (target_node == thumb) + return; + thumb->DragFrom(LayoutPoint(event->AbsoluteLocation())); +} + +void RangeInputType::HandleKeydownEvent(KeyboardEvent* event) { + if (GetElement().IsDisabledFormControl()) + return; + + const String& key = event->key(); + + const Decimal current = ParseToNumberOrNaN(GetElement().value()); + DCHECK(current.IsFinite()); + + StepRange step_range(CreateStepRange(kRejectAny)); + + // FIXME: We can't use stepUp() for the step value "any". So, we increase + // or decrease the value by 1/100 of the value range. Is it reasonable? + const Decimal step = DeprecatedEqualIgnoringCase( + GetElement().FastGetAttribute(stepAttr), "any") + ? (step_range.Maximum() - step_range.Minimum()) / 100 + : step_range.Step(); + const Decimal big_step = + std::max((step_range.Maximum() - step_range.Minimum()) / 10, step); + + TextDirection dir = TextDirection::kLtr; + bool is_vertical = false; + if (GetElement().GetLayoutObject()) { + dir = ComputedTextDirection(); + ControlPart part = GetElement().GetLayoutObject()->Style()->Appearance(); + is_vertical = part == kSliderVerticalPart; + } + + Decimal new_value; + if (key == "ArrowUp") { + new_value = current + step; + } else if (key == "ArrowDown") { + new_value = current - step; + } else if (key == "ArrowLeft") { + new_value = (is_vertical || dir == TextDirection::kRtl) ? current + step + : current - step; + } else if (key == "ArrowRight") { + new_value = (is_vertical || dir == TextDirection::kRtl) ? current - step + : current + step; + } else if (key == "PageUp") { + new_value = current + big_step; + } else if (key == "PageDown") { + new_value = current - big_step; + } else if (key == "Home") { + new_value = is_vertical ? step_range.Maximum() : step_range.Minimum(); + } else if (key == "End") { + new_value = is_vertical ? step_range.Minimum() : step_range.Maximum(); + } else { + return; // Did not match any key binding. + } + + new_value = step_range.ClampValue(new_value); + + if (new_value != current) { + EventQueueScope scope; + TextFieldEventBehavior event_behavior = kDispatchInputAndChangeEvent; + SetValueAsDecimal(new_value, event_behavior, IGNORE_EXCEPTION_FOR_TESTING); + + if (AXObjectCache* cache = + GetElement().GetDocument().ExistingAXObjectCache()) + cache->HandleValueChanged(&GetElement()); + } + + event->SetDefaultHandled(); +} + +void RangeInputType::CreateShadowSubtree() { + DCHECK(IsShadowHost(GetElement())); + + Document& document = GetElement().GetDocument(); + HTMLDivElement* track = HTMLDivElement::Create(document); + track->SetShadowPseudoId(AtomicString("-webkit-slider-runnable-track")); + track->setAttribute(idAttr, ShadowElementNames::SliderTrack()); + track->AppendChild(SliderThumbElement::Create(document)); + HTMLElement* container = SliderContainerElement::Create(document); + container->AppendChild(track); + GetElement().UserAgentShadowRoot()->AppendChild(container); + container->setAttribute(styleAttr, "-webkit-appearance:inherit"); +} + +LayoutObject* RangeInputType::CreateLayoutObject(const ComputedStyle&) const { + return new LayoutSlider(&GetElement()); +} + +Decimal RangeInputType::ParseToNumber(const String& src, + const Decimal& default_value) const { + return ParseToDecimalForNumberType(src, default_value); +} + +String RangeInputType::Serialize(const Decimal& value) const { + if (!value.IsFinite()) + return String(); + return SerializeForNumberType(value); +} + +// FIXME: Could share this with KeyboardClickableInputTypeView and +// BaseCheckableInputType if we had a common base class. +void RangeInputType::AccessKeyAction(bool send_mouse_events) { + InputTypeView::AccessKeyAction(send_mouse_events); + + GetElement().DispatchSimulatedClick( + nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents); +} + +void RangeInputType::SanitizeValueInResponseToMinOrMaxAttributeChange() { + if (GetElement().HasDirtyValue()) + GetElement().setValue(GetElement().value()); + else + GetElement().SetNonDirtyValue(GetElement().value()); + GetElement().UpdateView(); +} + +void RangeInputType::StepAttributeChanged() { + if (GetElement().HasDirtyValue()) + GetElement().setValue(GetElement().value()); + else + GetElement().SetNonDirtyValue(GetElement().value()); + GetElement().UpdateView(); +} + +void RangeInputType::DidSetValue(const String&, bool value_changed) { + if (value_changed) + GetElement().UpdateView(); +} + +void RangeInputType::UpdateView() { + GetSliderThumbElement()->SetPositionFromValue(); +} + +String RangeInputType::SanitizeValue(const String& proposed_value) const { + StepRange step_range(CreateStepRange(kRejectAny)); + const Decimal proposed_numeric_value = + ParseToNumber(proposed_value, step_range.DefaultValue()); + return SerializeForNumberType(step_range.ClampValue(proposed_numeric_value)); +} + +void RangeInputType::WarnIfValueIsInvalid(const String& value) const { + if (value.IsEmpty() || !GetElement().SanitizeValue(value).IsEmpty()) + return; + AddWarningToConsole( + "The specified value %s is not a valid number. The value must match to " + "the following regular expression: " + "-?(\\d+|\\d+\\.\\d+|\\.\\d+)([eE][-+]?\\d+)?", + value); +} + +void RangeInputType::DisabledAttributeChanged() { + if (GetElement().IsDisabledFormControl()) + GetSliderThumbElement()->StopDragging(); +} + +bool RangeInputType::ShouldRespectListAttribute() { + return true; +} + +inline SliderThumbElement* RangeInputType::GetSliderThumbElement() const { + return ToSliderThumbElementOrDie( + GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::SliderThumb())); +} + +inline Element* RangeInputType::SliderTrackElement() const { + return GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::SliderTrack()); +} + +void RangeInputType::ListAttributeTargetChanged() { + tick_mark_values_dirty_ = true; + if (GetElement().GetLayoutObject()) + GetElement() + .GetLayoutObject() + ->SetShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); + Element* slider_track_element = SliderTrackElement(); + if (slider_track_element->GetLayoutObject()) + slider_track_element->GetLayoutObject()->SetNeedsLayout( + LayoutInvalidationReason::kAttributeChanged); +} + +static bool DecimalCompare(const Decimal& a, const Decimal& b) { + return a < b; +} + +void RangeInputType::UpdateTickMarkValues() { + if (!tick_mark_values_dirty_) + return; + tick_mark_values_.clear(); + tick_mark_values_dirty_ = false; + HTMLDataListElement* data_list = GetElement().DataList(); + if (!data_list) + return; + HTMLDataListOptionsCollection* options = data_list->options(); + tick_mark_values_.ReserveCapacity(options->length()); + for (unsigned i = 0; i < options->length(); ++i) { + HTMLOptionElement* option_element = options->Item(i); + String option_value = option_element->value(); + if (option_element->IsDisabledFormControl() || option_value.IsEmpty()) + continue; + if (!GetElement().IsValidValue(option_value)) + continue; + tick_mark_values_.push_back(ParseToNumber(option_value, Decimal::Nan())); + } + tick_mark_values_.ShrinkToFit(); + std::sort(tick_mark_values_.begin(), tick_mark_values_.end(), DecimalCompare); +} + +Decimal RangeInputType::FindClosestTickMarkValue(const Decimal& value) { + UpdateTickMarkValues(); + if (!tick_mark_values_.size()) + return Decimal::Nan(); + + size_t left = 0; + size_t right = tick_mark_values_.size(); + size_t middle; + while (true) { + DCHECK_LE(left, right); + middle = left + (right - left) / 2; + if (!middle) + break; + if (middle == tick_mark_values_.size() - 1 && + tick_mark_values_[middle] < value) { + middle++; + break; + } + if (tick_mark_values_[middle - 1] <= value && + tick_mark_values_[middle] >= value) + break; + + if (tick_mark_values_[middle] < value) + left = middle; + else + right = middle; + } + const Decimal closest_left = middle ? tick_mark_values_[middle - 1] + : Decimal::Infinity(Decimal::kNegative); + const Decimal closest_right = middle != tick_mark_values_.size() + ? tick_mark_values_[middle] + : Decimal::Infinity(Decimal::kPositive); + if (closest_right - value < value - closest_left) + return closest_right; + return closest_left; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h new file mode 100644 index 00000000000..23c39a870fb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/range_input_type.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RANGE_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RANGE_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/input_type_view.h" + +namespace blink { + +class ExceptionState; +class SliderThumbElement; + +class RangeInputType final : public InputType, public InputTypeView { + USING_GARBAGE_COLLECTED_MIXIN(RangeInputType); + + public: + static InputType* Create(HTMLInputElement&); + void Trace(blink::Visitor*) override; + using InputType::GetElement; + + private: + RangeInputType(HTMLInputElement&); + InputTypeView* CreateView() override; + ValueMode GetValueMode() const override; + void CountUsage() override; + const AtomicString& FormControlType() const override; + double ValueAsDouble() const override; + void SetValueAsDouble(double, + TextFieldEventBehavior, + ExceptionState&) const override; + bool TypeMismatchFor(const String&) const override; + bool SupportsRequired() const override; + StepRange CreateStepRange(AnyStepHandling) const override; + bool IsSteppable() const override; + void HandleMouseDownEvent(MouseEvent*) override; + void HandleKeydownEvent(KeyboardEvent*) override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + void CreateShadowSubtree() override; + Decimal ParseToNumber(const String&, const Decimal&) const override; + String Serialize(const Decimal&) const override; + void AccessKeyAction(bool send_mouse_events) override; + void SanitizeValueInResponseToMinOrMaxAttributeChange() override; + void StepAttributeChanged() override; + void WarnIfValueIsInvalid(const String&) const override; + void DidSetValue(const String&, bool value_changed) override; + String SanitizeValue(const String& proposed_value) const override; + bool ShouldRespectListAttribute() override; + void DisabledAttributeChanged() override; + void ListAttributeTargetChanged() override; + Decimal FindClosestTickMarkValue(const Decimal&) override; + + SliderThumbElement* GetSliderThumbElement() const; + Element* SliderTrackElement() const; + void UpdateTickMarkValues(); + + // InputTypeView function: + void UpdateView() override; + + bool tick_mark_values_dirty_; + Vector<Decimal> tick_mark_values_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RANGE_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc new file mode 100644 index 00000000000..b1093eb2d12 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.cc @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/reset_input_type.h" + +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +InputType* ResetInputType::Create(HTMLInputElement& element) { + return new ResetInputType(element); +} + +const AtomicString& ResetInputType::FormControlType() const { + return InputTypeNames::reset; +} + +bool ResetInputType::SupportsValidation() const { + return false; +} + +void ResetInputType::HandleDOMActivateEvent(Event* event) { + if (GetElement().IsDisabledFormControl() || !GetElement().Form()) + return; + GetElement().Form()->reset(); + event->SetDefaultHandled(); +} + +String ResetInputType::DefaultLabel() const { + return GetLocale().QueryString(WebLocalizedString::kResetButtonDefaultLabel); +} + +bool ResetInputType::IsTextButton() const { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h new file mode 100644 index 00000000000..e2c58bc3f47 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/reset_input_type.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RESET_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RESET_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h" + +namespace blink { + +class ResetInputType final : public BaseButtonInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + ResetInputType(HTMLInputElement& element) : BaseButtonInputType(element) {} + const AtomicString& FormControlType() const override; + bool SupportsValidation() const override; + void HandleDOMActivateEvent(Event*) override; + String DefaultLabel() const override; + bool IsTextButton() const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_RESET_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format b/chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format new file mode 100644 index 00000000000..ea647be8464 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/.clang-format @@ -0,0 +1,8 @@ +--- +BasedOnStyle: Chromium +Language: JavaScript +ColumnLimit: 120 +CommentPragmas: .*\@.* +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +ReflowComments: false diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css new file mode 100644 index 00000000000..e5cbb72003b --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.css @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +body { + -webkit-user-select: none; + background-color: white; + font: -webkit-control; + font-size: 12px; +} + +.rtl { + direction: rtl; +} + +.scroll-view { + overflow: hidden; + width: 0; + height: 0; +} + +.list-cell { + position: absolute; + left: 0; + top: 0; + width: 0; + height: 0; +} + +.list-cell.hidden { + display: none; +} + +.week-number-cell, +.day-cell { + position: static; + text-align: center; + box-sizing: border-box; + display: inline-block; + cursor: default; + transition: color 1s; + padding: 1px; +} + +.week-number-cell { + box-sizing: border-box; + color: black; + padding-right: 0; + box-shadow: 1px 0 0 #bfbfbf; + margin-right: 1px; +} + +.day-cell { + color: #bfbfbf; +} + +.day-cell.highlighted.today, +.day-cell.today { + border: 1px solid #bfbfbf; + padding: 0; +} + +.week-number-cell.highlighted, +.day-cell.highlighted { + background-color: #e5ecf8; +} + +.week-number-cell.highlighted.disabled, +.day-cell.highlighted.disabled { + border: 1px solid #e5ecf8; + padding: 0; +} + +.week-number-cell.selected, +.day-cell.selected { + background-color: #bccdec; +} + +.week-number-cell.disabled, +.day-cell.disabled { + background-color: #f5f5f5; +} + +.day-cell.current-month { + color: #000000; +} + +.calendar-table-view { + border: 1px solid #bfbfbf; + outline: none; +} + +.week-number-label, +.week-day-label { + text-align: center; + display: inline-block; + line-height: 23px; + padding-top: 1px; + box-sizing: padding-box; +} + +.week-number-label { + box-sizing: border-box; + border-right: 1px solid #bfbfbf; +} + +.calendar-table-header-view { + background-color: #f5f5f5; + border-bottom: 1px solid #bfbfbf; + height: 24px; +} + +.calendar-picker { + border: 1px solid #bfbfbf; + border-radius: 2px; + position: absolute; + padding: 10px; + background-color: white; + overflow: hidden; + cursor: default; +} + +.calendar-header-view { + margin-bottom: 10px; + display: flex; + flex-flow: row; +} + +.calendar-title { + -webkit-align-self: center; + flex: 1; + text-align: left; +} + +.rtl .calendar-title { + text-align: right; +} + +.month-popup-button, +.month-popup-button:hover, +.month-popup-button:disabled { + background-color: transparent !important; + background-image: none !important; + box-shadow: none !important; + color: black; +} + +.month-popup-button:disabled { + opacity: 0.7; +} + +.month-popup-button { + font-size: 12px; + padding: 4px; + display: inline-block; + cursor: default; + border: 1px solid transparent !important; + height: 24px !important; +} + +.month-popup-button .disclosure-triangle { + margin: 0 6px; +} + +.month-popup-button .disclosure-triangle svg { + padding-bottom: 2px; +} + +.today-button::after { + content: ""; + display: block; + border-radius: 3px; + width: 6px; + height: 6px; + background-color: #6e6e6e; + margin: 0 auto; +} + +.calendar-navigation-button { + -webkit-align-self: center; + width: 24px; + height: 24px; + min-width: 0 !important; + padding-left: 0 !important; + padding-right: 0 !important; + -webkit-margin-start: 4px !important; +} + +.year-list-view { + border: 1px solid #bfbfbf; + background-color: white; + position: absolute; +} + +.year-list-cell { + box-sizing: border-box; + border-bottom: 1px solid #bfbfbf; + background-color: white; + overflow: hidden; +} + +.year-list-cell .label { + height: 24px; + line-height: 24px; + -webkit-padding-start: 8px; + background-color: #f5f5f5; + border-bottom: 1px solid #bfbfbf; +} + +.year-list-cell .month-chooser { + padding: 0; +} + +.month-buttons-row { + display: flex; +} + +.month-button { + flex: 1; + height: 32px; + line-height: 32px; + padding: 0 !important; + margin: 0 !important; + background-image: none !important; + background-color: #ffffff; + border-width: 0 !important; + box-shadow: none !important; + text-align: center; +} + +.month-button.highlighted { + background-color: #e5ecf8; +} + +.month-button[aria-disabled="true"] { + color: GrayText; +} + +.scrubby-scroll-bar { + width: 14px; + height: 60px; + background-color: white; + border-left: 1px solid #bfbfbf; + position: absolute; + top: 0; +} + +.scrubby-scroll-thumb { + width: 10px; + margin: 2px; + height: 30px; + background-color: #d8d8d8; + position: absolute; + left: 0; + top: 0; +} + +.month-popup-view { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.year-list-view .scrubby-scroll-bar { + right: 0; +} + +.rtl .year-list-view .scrubby-scroll-bar { + left: 0; + right: auto; + border-left-width: 0; + border-right: 1px solid #bfbfbf; +} + +.year-month-button { + width: 24px; + height: 24px; + min-width: 0; + padding: 0; +} + +.month-popup-button:focus, +.year-list-view:focus, +.calendar-table-view:focus { + transition: border-color 200ms; + /* We use border color because it follows the border radius (unlike outline). + * This is particularly noticeable on mac. */ + border-color: rgb(77, 144, 254) !important; + outline: none; +} + +.preparing button:focus, +.preparing .year-list-view:focus, +.preparing .calendar-table-view:focus { + transition: none; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js new file mode 100644 index 00000000000..250b55c6ad7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/calendarPicker.js @@ -0,0 +1,4085 @@ +'use strict'; +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @enum {number} + */ +var WeekDay = {Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6}; + +/** + * @type {Object} + */ +var global = { + picker: null, + params: { + locale: 'en-US', + weekStartDay: WeekDay.Sunday, + dayLabels: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + shortMonthLabels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'], + isLocaleRTL: false, + mode: 'date', + weekLabel: 'Week', + anchorRectInScreen: new Rectangle(0, 0, 0, 0), + currentValue: null + } +}; + +// ---------------------------------------------------------------- +// Utility functions + +/** + * @return {!boolean} + */ +function hasInaccuratePointingDevice() { + return matchMedia('(any-pointer: coarse)').matches; +} + +/** + * @return {!string} lowercase locale name. e.g. "en-us" + */ +function getLocale() { + return (global.params.locale || 'en-us').toLowerCase(); +} + +/** + * @return {!string} lowercase language code. e.g. "en" + */ +function getLanguage() { + var locale = getLocale(); + var result = locale.match(/^([a-z]+)/); + if (!result) + return 'en'; + return result[1]; +} + +/** + * @param {!number} number + * @return {!string} + */ +function localizeNumber(number) { + return window.pagePopupController.localizeNumberString(number); +} + +/** + * @const + * @type {number} + */ +var ImperialEraLimit = 2087; + +/** + * @param {!number} year + * @param {!number} month + * @return {!string} + */ +function formatJapaneseImperialEra(year, month) { + // We don't show an imperial era if it is greater than 99 becase of space + // limitation. + if (year > ImperialEraLimit) + return ''; + if (year > 1989) + return '(\u5e73\u6210' + localizeNumber(year - 1988) + '\u5e74)'; + if (year == 1989) + return '(\u5e73\u6210\u5143\u5e74)'; + if (year >= 1927) + return '(\u662d\u548c' + localizeNumber(year - 1925) + '\u5e74)'; + if (year > 1912) + return '(\u5927\u6b63' + localizeNumber(year - 1911) + '\u5e74)'; + if (year == 1912 && month >= 7) + return '(\u5927\u6b63\u5143\u5e74)'; + if (year > 1868) + return '(\u660e\u6cbb' + localizeNumber(year - 1867) + '\u5e74)'; + if (year == 1868) + return '(\u660e\u6cbb\u5143\u5e74)'; + return ''; +} + +function createUTCDate(year, month, date) { + var newDate = new Date(0); + newDate.setUTCFullYear(year); + newDate.setUTCMonth(month); + newDate.setUTCDate(date); + return newDate; +} + +/** + * @param {string} dateString + * @return {?Day|Week|Month} + */ +function parseDateString(dateString) { + var month = Month.parse(dateString); + if (month) + return month; + var week = Week.parse(dateString); + if (week) + return week; + return Day.parse(dateString); +} + +/** + * @const + * @type {number} + */ +var DaysPerWeek = 7; + +/** + * @const + * @type {number} + */ +var MonthsPerYear = 12; + +/** + * @const + * @type {number} + */ +var MillisecondsPerDay = 24 * 60 * 60 * 1000; + +/** + * @const + * @type {number} + */ +var MillisecondsPerWeek = DaysPerWeek * MillisecondsPerDay; + +/** + * @constructor + */ +function DateType() { +} + +/** + * @constructor + * @extends DateType + * @param {!number} year + * @param {!number} month + * @param {!number} date + */ +function Day(year, month, date) { + var dateObject = createUTCDate(year, month, date); + if (isNaN(dateObject.valueOf())) + throw 'Invalid date'; + /** + * @type {number} + * @const + */ + this.year = dateObject.getUTCFullYear(); + /** + * @type {number} + * @const + */ + this.month = dateObject.getUTCMonth(); + /** + * @type {number} + * @const + */ + this.date = dateObject.getUTCDate(); +}; + +Day.prototype = Object.create(DateType.prototype); + +Day.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)/; + +/** + * @param {!string} str + * @return {?Day} + */ +Day.parse = function(str) { + var match = Day.ISOStringRegExp.exec(str); + if (!match) + return null; + var year = parseInt(match[1], 10); + var month = parseInt(match[2], 10) - 1; + var date = parseInt(match[3], 10); + return new Day(year, month, date); +}; + +/** + * @param {!number} value + * @return {!Day} + */ +Day.createFromValue = function(millisecondsSinceEpoch) { + return Day.createFromDate(new Date(millisecondsSinceEpoch)) +}; + +/** + * @param {!Date} date + * @return {!Day} + */ +Day.createFromDate = function(date) { + if (isNaN(date.valueOf())) + throw 'Invalid date'; + return new Day(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); +}; + +/** + * @param {!Day} day + * @return {!Day} + */ +Day.createFromDay = function(day) { + return day; +}; + +/** + * @return {!Day} + */ +Day.createFromToday = function() { + var now = new Date(); + return new Day(now.getFullYear(), now.getMonth(), now.getDate()); +}; + +/** + * @param {!DateType} other + * @return {!boolean} + */ +Day.prototype.equals = function(other) { + return other instanceof Day && this.year === other.year && this.month === other.month && this.date === other.date; +}; + +/** + * @param {!number=} offset + * @return {!Day} + */ +Day.prototype.previous = function(offset) { + if (typeof offset === 'undefined') + offset = 1; + return new Day(this.year, this.month, this.date - offset); +}; + +/** + * @param {!number=} offset + * @return {!Day} + */ +Day.prototype.next = function(offset) { + if (typeof offset === 'undefined') + offset = 1; + return new Day(this.year, this.month, this.date + offset); +}; + +/** + * @return {!Date} + */ +Day.prototype.startDate = function() { + return createUTCDate(this.year, this.month, this.date); +}; + +/** + * @return {!Date} + */ +Day.prototype.endDate = function() { + return createUTCDate(this.year, this.month, this.date + 1); +}; + +/** + * @return {!Day} + */ +Day.prototype.firstDay = function() { + return this; +}; + +/** + * @return {!Day} + */ +Day.prototype.middleDay = function() { + return this; +}; + +/** + * @return {!Day} + */ +Day.prototype.lastDay = function() { + return this; +}; + +/** + * @return {!number} + */ +Day.prototype.valueOf = function() { + return createUTCDate(this.year, this.month, this.date).getTime(); +}; + +/** + * @return {!WeekDay} + */ +Day.prototype.weekDay = function() { + return createUTCDate(this.year, this.month, this.date).getUTCDay(); +}; + +/** + * @return {!string} + */ +Day.prototype.toString = function() { + var yearString = String(this.year); + if (yearString.length < 4) + yearString = ('000' + yearString).substr(-4, 4); + return yearString + '-' + ('0' + (this.month + 1)).substr(-2, 2) + '-' + ('0' + this.date).substr(-2, 2); +}; + +/** + * @return {!string} + */ +Day.prototype.format = function() { + if (!Day.formatter) { + Day.formatter = new Intl.DateTimeFormat( + getLocale(), {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC'}); + } + return Day.formatter.format(this.startDate()); +}; + +// See WebCore/platform/DateComponents.h. +Day.Minimum = Day.createFromValue(-62135596800000.0); +Day.Maximum = Day.createFromValue(8640000000000000.0); + +// See WebCore/html/DayInputType.cpp. +Day.DefaultStep = 86400000; +Day.DefaultStepBase = 0; + +/** + * @constructor + * @extends DateType + * @param {!number} year + * @param {!number} week + */ +function Week(year, week) { + /** + * @type {number} + * @const + */ + this.year = year; + /** + * @type {number} + * @const + */ + this.week = week; + // Number of years per year is either 52 or 53. + if (this.week < 1 || (this.week > 52 && this.week > Week.numberOfWeeksInYear(this.year))) { + var normalizedWeek = Week.createFromDay(this.firstDay()); + this.year = normalizedWeek.year; + this.week = normalizedWeek.week; + } +} + +Week.ISOStringRegExp = /^(\d+)-[wW](\d+)$/; + +// See WebCore/platform/DateComponents.h. +Week.Minimum = new Week(1, 1); +Week.Maximum = new Week(275760, 37); + +// See WebCore/html/WeekInputType.cpp. +Week.DefaultStep = 604800000; +Week.DefaultStepBase = -259200000; + +Week.EpochWeekDay = createUTCDate(1970, 0, 0).getUTCDay(); + +/** + * @param {!string} str + * @return {?Week} + */ +Week.parse = function(str) { + var match = Week.ISOStringRegExp.exec(str); + if (!match) + return null; + var year = parseInt(match[1], 10); + var week = parseInt(match[2], 10); + return new Week(year, week); +}; + +/** + * @param {!number} millisecondsSinceEpoch + * @return {!Week} + */ +Week.createFromValue = function(millisecondsSinceEpoch) { + return Week.createFromDate(new Date(millisecondsSinceEpoch)) +}; + +/** + * @param {!Date} date + * @return {!Week} + */ +Week.createFromDate = function(date) { + if (isNaN(date.valueOf())) + throw 'Invalid date'; + var year = date.getUTCFullYear(); + if (year <= Week.Maximum.year && Week.weekOneStartDateForYear(year + 1).getTime() <= date.getTime()) + year++; + else if (year > 1 && Week.weekOneStartDateForYear(year).getTime() > date.getTime()) + year--; + var week = 1 + Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), date); + return new Week(year, week); +}; + +/** + * @param {!Day} day + * @return {!Week} + */ +Week.createFromDay = function(day) { + var year = day.year; + if (year <= Week.Maximum.year && Week.weekOneStartDayForYear(year + 1) <= day) + year++; + else if (year > 1 && Week.weekOneStartDayForYear(year) > day) + year--; + var week = Math.floor(1 + (day.valueOf() - Week.weekOneStartDayForYear(year).valueOf()) / MillisecondsPerWeek); + return new Week(year, week); +}; + +/** + * @return {!Week} + */ +Week.createFromToday = function() { + var now = new Date(); + return Week.createFromDate(createUTCDate(now.getFullYear(), now.getMonth(), now.getDate())); +}; + +/** + * @param {!number} year + * @return {!Date} + */ +Week.weekOneStartDateForYear = function(year) { + if (year < 1) + return createUTCDate(1, 0, 1); + // The week containing January 4th is week one. + var yearStartDay = createUTCDate(year, 0, 4).getUTCDay(); + return createUTCDate(year, 0, 4 - (yearStartDay + 6) % DaysPerWeek); +}; + +/** + * @param {!number} year + * @return {!Day} + */ +Week.weekOneStartDayForYear = function(year) { + if (year < 1) + return Day.Minimum; + // The week containing January 4th is week one. + var yearStartDay = createUTCDate(year, 0, 4).getUTCDay(); + return new Day(year, 0, 4 - (yearStartDay + 6) % DaysPerWeek); +}; + +/** + * @param {!number} year + * @return {!number} + */ +Week.numberOfWeeksInYear = function(year) { + if (year < 1 || year > Week.Maximum.year) + return 0; + else if (year === Week.Maximum.year) + return Week.Maximum.week; + return Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), Week.weekOneStartDateForYear(year + 1)); +}; + +/** + * @param {!Date} baseDate + * @param {!Date} date + * @return {!number} + */ +Week._numberOfWeeksSinceDate = function(baseDate, date) { + return Math.floor((date.getTime() - baseDate.getTime()) / MillisecondsPerWeek); +}; + +/** + * @param {!DateType} other + * @return {!boolean} + */ +Week.prototype.equals = function(other) { + return other instanceof Week && this.year === other.year && this.week === other.week; +}; + +/** + * @param {!number=} offset + * @return {!Week} + */ +Week.prototype.previous = function(offset) { + if (typeof offset === 'undefined') + offset = 1; + return new Week(this.year, this.week - offset); +}; + +/** + * @param {!number=} offset + * @return {!Week} + */ +Week.prototype.next = function(offset) { + if (typeof offset === 'undefined') + offset = 1; + return new Week(this.year, this.week + offset); +}; + +/** + * @return {!Date} + */ +Week.prototype.startDate = function() { + var weekStartDate = Week.weekOneStartDateForYear(this.year); + weekStartDate.setUTCDate(weekStartDate.getUTCDate() + (this.week - 1) * 7); + return weekStartDate; +}; + +/** + * @return {!Date} + */ +Week.prototype.endDate = function() { + if (this.equals(Week.Maximum)) + return Day.Maximum.startDate(); + return this.next().startDate(); +}; + +/** + * @return {!Day} + */ +Week.prototype.firstDay = function() { + var weekOneStartDay = Week.weekOneStartDayForYear(this.year); + return weekOneStartDay.next((this.week - 1) * DaysPerWeek); +}; + +/** + * @return {!Day} + */ +Week.prototype.middleDay = function() { + return this.firstDay().next(3); +}; + +/** + * @return {!Day} + */ +Week.prototype.lastDay = function() { + if (this.equals(Week.Maximum)) + return Day.Maximum; + return this.next().firstDay().previous(); +}; + +/** + * @return {!number} + */ +Week.prototype.valueOf = function() { + return this.firstDay().valueOf() - createUTCDate(1970, 0, 1).getTime(); +}; + +/** + * @return {!string} + */ +Week.prototype.toString = function() { + var yearString = String(this.year); + if (yearString.length < 4) + yearString = ('000' + yearString).substr(-4, 4); + return yearString + '-W' + ('0' + this.week).substr(-2, 2); +}; + +/** + * @constructor + * @extends DateType + * @param {!number} year + * @param {!number} month + */ +function Month(year, month) { + /** + * @type {number} + * @const + */ + this.year = year + Math.floor(month / MonthsPerYear); + /** + * @type {number} + * @const + */ + this.month = month % MonthsPerYear < 0 ? month % MonthsPerYear + MonthsPerYear : month % MonthsPerYear; +}; + +Month.ISOStringRegExp = /^(\d+)-(\d+)$/; + +// See WebCore/platform/DateComponents.h. +Month.Minimum = new Month(1, 0); +Month.Maximum = new Month(275760, 8); + +// See WebCore/html/MonthInputType.cpp. +Month.DefaultStep = 1; +Month.DefaultStepBase = 0; + +/** + * @param {!string} str + * @return {?Month} + */ +Month.parse = function(str) { + var match = Month.ISOStringRegExp.exec(str); + if (!match) + return null; + var year = parseInt(match[1], 10); + var month = parseInt(match[2], 10) - 1; + return new Month(year, month); +}; + +/** + * @param {!number} value + * @return {!Month} + */ +Month.createFromValue = function(monthsSinceEpoch) { + return new Month(1970, monthsSinceEpoch) +}; + +/** + * @param {!Date} date + * @return {!Month} + */ +Month.createFromDate = function(date) { + if (isNaN(date.valueOf())) + throw 'Invalid date'; + return new Month(date.getUTCFullYear(), date.getUTCMonth()); +}; + +/** + * @param {!Day} day + * @return {!Month} + */ +Month.createFromDay = function(day) { + return new Month(day.year, day.month); +}; + +/** + * @return {!Month} + */ +Month.createFromToday = function() { + var now = new Date(); + return new Month(now.getFullYear(), now.getMonth()); +}; + +/** + * @return {!boolean} + */ +Month.prototype.containsDay = function(day) { + return this.year === day.year && this.month === day.month; +}; + +/** + * @param {!Month} other + * @return {!boolean} + */ +Month.prototype.equals = function(other) { + return other instanceof Month && this.year === other.year && this.month === other.month; +}; + +/** + * @param {!number=} offset + * @return {!Month} + */ +Month.prototype.previous = function(offset) { + if (typeof offset === 'undefined') + offset = 1; + return new Month(this.year, this.month - offset); +}; + +/** + * @param {!number=} offset + * @return {!Month} + */ +Month.prototype.next = function(offset) { + if (typeof offset === 'undefined') + offset = 1; + return new Month(this.year, this.month + offset); +}; + +/** + * @return {!Date} + */ +Month.prototype.startDate = function() { + return createUTCDate(this.year, this.month, 1); +}; + +/** + * @return {!Date} + */ +Month.prototype.endDate = function() { + if (this.equals(Month.Maximum)) + return Day.Maximum.startDate(); + return this.next().startDate(); +}; + +/** + * @return {!Day} + */ +Month.prototype.firstDay = function() { + return new Day(this.year, this.month, 1); +}; + +/** + * @return {!Day} + */ +Month.prototype.middleDay = function() { + return new Day(this.year, this.month, this.month === 2 ? 14 : 15); +}; + +/** + * @return {!Day} + */ +Month.prototype.lastDay = function() { + if (this.equals(Month.Maximum)) + return Day.Maximum; + return this.next().firstDay().previous(); +}; + +/** + * @return {!number} + */ +Month.prototype.valueOf = function() { + return (this.year - 1970) * MonthsPerYear + this.month; +}; + +/** + * @return {!string} + */ +Month.prototype.toString = function() { + var yearString = String(this.year); + if (yearString.length < 4) + yearString = ('000' + yearString).substr(-4, 4); + return yearString + '-' + ('0' + (this.month + 1)).substr(-2, 2); +}; + +/** + * @return {!string} + */ +Month.prototype.toLocaleString = function() { + if (global.params.locale === 'ja') + return '' + this.year + '\u5e74' + formatJapaneseImperialEra(this.year, this.month) + ' ' + (this.month + 1) + + '\u6708'; + return window.pagePopupController.formatMonth(this.year, this.month); +}; + +/** + * @return {!string} + */ +Month.prototype.toShortLocaleString = function() { + return window.pagePopupController.formatShortMonth(this.year, this.month); +}; + +// ---------------------------------------------------------------- +// Initialization + +/** + * @param {Event} event + */ +function handleMessage(event) { + if (global.argumentsReceived) + return; + global.argumentsReceived = true; + initialize(JSON.parse(event.data)); +} + +/** + * @param {!Object} params + */ +function setGlobalParams(params) { + var name; + for (name in global.params) { + if (typeof params[name] === 'undefined') + console.warn('Missing argument: ' + name); + } + for (name in params) { + global.params[name] = params[name]; + } +}; + +/** + * @param {!Object} args + */ +function initialize(args) { + setGlobalParams(args); + if (global.params.suggestionValues && global.params.suggestionValues.length) + openSuggestionPicker(); + else + openCalendarPicker(); +} + +function closePicker() { + if (global.picker) + global.picker.cleanup(); + var main = $('main'); + main.innerHTML = ''; + main.className = ''; +}; + +function openSuggestionPicker() { + closePicker(); + global.picker = new SuggestionPicker($('main'), global.params); +}; + +function openCalendarPicker() { + closePicker(); + global.picker = new CalendarPicker(global.params.mode, global.params); + global.picker.attachTo($('main')); +}; + +// Parameter t should be a number between 0 and 1. +var AnimationTimingFunction = { + Linear: function(t) { + return t; + }, + EaseInOut: function(t) { + t *= 2; + if (t < 1) + return Math.pow(t, 3) / 2; + t -= 2; + return Math.pow(t, 3) / 2 + 1; + } +}; + +/** + * @constructor + * @extends EventEmitter + */ +function AnimationManager() { + EventEmitter.call(this); + + this._isRunning = false; + this._runningAnimatorCount = 0; + this._runningAnimators = {}; + this._animationFrameCallbackBound = this._animationFrameCallback.bind(this); +} + +AnimationManager.prototype = Object.create(EventEmitter.prototype); + +AnimationManager.EventTypeAnimationFrameWillFinish = 'animationFrameWillFinish'; + +AnimationManager.prototype._startAnimation = function() { + if (this._isRunning) + return; + this._isRunning = true; + window.requestAnimationFrame(this._animationFrameCallbackBound); +}; + +AnimationManager.prototype._stopAnimation = function() { + if (!this._isRunning) + return; + this._isRunning = false; +}; + +/** + * @param {!Animator} animator + */ +AnimationManager.prototype.add = function(animator) { + if (this._runningAnimators[animator.id]) + return; + this._runningAnimators[animator.id] = animator; + this._runningAnimatorCount++; + if (this._needsTimer()) + this._startAnimation(); +}; + +/** + * @param {!Animator} animator + */ +AnimationManager.prototype.remove = function(animator) { + if (!this._runningAnimators[animator.id]) + return; + delete this._runningAnimators[animator.id]; + this._runningAnimatorCount--; + if (!this._needsTimer()) + this._stopAnimation(); +}; + +AnimationManager.prototype._animationFrameCallback = function(now) { + if (this._runningAnimatorCount > 0) { + for (var id in this._runningAnimators) { + this._runningAnimators[id].onAnimationFrame(now); + } + } + this.dispatchEvent(AnimationManager.EventTypeAnimationFrameWillFinish); + if (this._isRunning) + window.requestAnimationFrame(this._animationFrameCallbackBound); +}; + +/** + * @return {!boolean} + */ +AnimationManager.prototype._needsTimer = function() { + return this._runningAnimatorCount > 0 || this.hasListener(AnimationManager.EventTypeAnimationFrameWillFinish); +}; + +/** + * @param {!string} type + * @param {!Function} callback + * @override + */ +AnimationManager.prototype.on = function(type, callback) { + EventEmitter.prototype.on.call(this, type, callback); + if (this._needsTimer()) + this._startAnimation(); +}; + +/** + * @param {!string} type + * @param {!Function} callback + * @override + */ +AnimationManager.prototype.removeListener = function(type, callback) { + EventEmitter.prototype.removeListener.call(this, type, callback); + if (!this._needsTimer()) + this._stopAnimation(); +}; + +AnimationManager.shared = new AnimationManager(); + +/** + * @constructor + * @extends EventEmitter + */ +function Animator() { + EventEmitter.call(this); + + /** + * @type {!number} + * @const + */ + this.id = Animator._lastId++; + /** + * @type {!number} + */ + this.duration = 100; + /** + * @type {?function} + */ + this.step = null; + /** + * @type {!boolean} + * @protected + */ + this._isRunning = false; + /** + * @type {!number} + */ + this.currentValue = 0; + /** + * @type {!number} + * @protected + */ + this._lastStepTime = 0; +} + +Animator.prototype = Object.create(EventEmitter.prototype); + +Animator._lastId = 0; + +Animator.EventTypeDidAnimationStop = 'didAnimationStop'; + +/** + * @return {!boolean} + */ +Animator.prototype.isRunning = function() { + return this._isRunning; +}; + +Animator.prototype.start = function() { + this._lastStepTime = performance.now(); + this._isRunning = true; + AnimationManager.shared.add(this); +}; + +Animator.prototype.stop = function() { + if (!this._isRunning) + return; + this._isRunning = false; + AnimationManager.shared.remove(this); + this.dispatchEvent(Animator.EventTypeDidAnimationStop, this); +}; + +/** + * @param {!number} now + */ +Animator.prototype.onAnimationFrame = function(now) { + this._lastStepTime = now; + this.step(this); +}; + +/** + * @constructor + * @extends Animator + */ +function TransitionAnimator() { + Animator.call(this); + /** + * @type {!number} + * @protected + */ + this._from = 0; + /** + * @type {!number} + * @protected + */ + this._to = 0; + /** + * @type {!number} + * @protected + */ + this._delta = 0; + /** + * @type {!number} + */ + this.progress = 0.0; + /** + * @type {!function} + */ + this.timingFunction = AnimationTimingFunction.Linear; +} + +TransitionAnimator.prototype = Object.create(Animator.prototype); + +/** + * @param {!number} value + */ +TransitionAnimator.prototype.setFrom = function(value) { + this._from = value; + this._delta = this._to - this._from; +}; + +TransitionAnimator.prototype.start = function() { + console.assert(isFinite(this.duration)); + this.progress = 0.0; + this.currentValue = this._from; + Animator.prototype.start.call(this); +}; + +/** + * @param {!number} value + */ +TransitionAnimator.prototype.setTo = function(value) { + this._to = value; + this._delta = this._to - this._from; +}; + +/** + * @param {!number} now + */ +TransitionAnimator.prototype.onAnimationFrame = function(now) { + this.progress += (now - this._lastStepTime) / this.duration; + this.progress = Math.min(1.0, this.progress); + this._lastStepTime = now; + this.currentValue = this.timingFunction(this.progress) * this._delta + this._from; + this.step(this); + if (this.progress === 1.0) { + this.stop(); + return; + } +}; + +/** + * @constructor + * @extends Animator + * @param {!number} initialVelocity + * @param {!number} initialValue + */ +function FlingGestureAnimator(initialVelocity, initialValue) { + Animator.call(this); + /** + * @type {!number} + */ + this.initialVelocity = initialVelocity; + /** + * @type {!number} + */ + this.initialValue = initialValue; + /** + * @type {!number} + * @protected + */ + this._elapsedTime = 0; + var startVelocity = Math.abs(this.initialVelocity); + if (startVelocity > this._velocityAtTime(0)) + startVelocity = this._velocityAtTime(0); + if (startVelocity < 0) + startVelocity = 0; + /** + * @type {!number} + * @protected + */ + this._timeOffset = this._timeAtVelocity(startVelocity); + /** + * @type {!number} + * @protected + */ + this._positionOffset = this._valueAtTime(this._timeOffset); + /** + * @type {!number} + */ + this.duration = this._timeAtVelocity(0); +} + +FlingGestureAnimator.prototype = Object.create(Animator.prototype); + +// Velocity is subject to exponential decay. These parameters are coefficients +// that determine the curve. +FlingGestureAnimator._P0 = -5707.62; +FlingGestureAnimator._P1 = 0.172; +FlingGestureAnimator._P2 = 0.0037; + +/** + * @param {!number} t + */ +FlingGestureAnimator.prototype._valueAtTime = function(t) { + return FlingGestureAnimator._P0 * Math.exp(-FlingGestureAnimator._P2 * t) - FlingGestureAnimator._P1 * t - + FlingGestureAnimator._P0; +}; + +/** + * @param {!number} t + */ +FlingGestureAnimator.prototype._velocityAtTime = function(t) { + return -FlingGestureAnimator._P0 * FlingGestureAnimator._P2 * Math.exp(-FlingGestureAnimator._P2 * t) - + FlingGestureAnimator._P1; +}; + +/** + * @param {!number} v + */ +FlingGestureAnimator.prototype._timeAtVelocity = function(v) { + return -Math.log((v + FlingGestureAnimator._P1) / (-FlingGestureAnimator._P0 * FlingGestureAnimator._P2)) / + FlingGestureAnimator._P2; +}; + +FlingGestureAnimator.prototype.start = function() { + this._lastStepTime = performance.now(); + Animator.prototype.start.call(this); +}; + +/** + * @param {!number} now + */ +FlingGestureAnimator.prototype.onAnimationFrame = function(now) { + this._elapsedTime += now - this._lastStepTime; + this._lastStepTime = now; + if (this._elapsedTime + this._timeOffset >= this.duration) { + this.stop(); + return; + } + var position = this._valueAtTime(this._elapsedTime + this._timeOffset) - this._positionOffset; + if (this.initialVelocity < 0) + position = -position; + this.currentValue = position + this.initialValue; + this.step(this); +}; + +/** + * @constructor + * @extends EventEmitter + * @param {?Element} element + * View adds itself as a property on the element so we can access it from Event.target. + */ +function View(element) { + EventEmitter.call(this); + /** + * @type {Element} + * @const + */ + this.element = element || createElement('div'); + this.element.$view = this; + this.bindCallbackMethods(); +} + +View.prototype = Object.create(EventEmitter.prototype); + +/** + * @param {!Element} ancestorElement + * @return {?Object} + */ +View.prototype.offsetRelativeTo = function(ancestorElement) { + var x = 0; + var y = 0; + var element = this.element; + while (element) { + x += element.offsetLeft || 0; + y += element.offsetTop || 0; + element = element.offsetParent; + if (element === ancestorElement) + return {x: x, y: y}; + } + return null; +}; + +/** + * @param {!View|Node} parent + * @param {?View|Node=} before + */ +View.prototype.attachTo = function(parent, before) { + if (parent instanceof View) + return this.attachTo(parent.element, before); + if (typeof before === 'undefined') + before = null; + if (before instanceof View) + before = before.element; + parent.insertBefore(this.element, before); +}; + +View.prototype.bindCallbackMethods = function() { + for (var methodName in this) { + if (!/^on[A-Z]/.test(methodName)) + continue; + if (this.hasOwnProperty(methodName)) + continue; + var method = this[methodName]; + if (!(method instanceof Function)) + continue; + this[methodName] = method.bind(this); + } +}; + +/** + * @constructor + * @extends View + */ +function ScrollView() { + View.call(this, createElement('div', ScrollView.ClassNameScrollView)); + /** + * @type {Element} + * @const + */ + this.contentElement = createElement('div', ScrollView.ClassNameScrollViewContent); + this.element.appendChild(this.contentElement); + /** + * @type {number} + */ + this.minimumContentOffset = -Infinity; + /** + * @type {number} + */ + this.maximumContentOffset = Infinity; + /** + * @type {number} + * @protected + */ + this._contentOffset = 0; + /** + * @type {number} + * @protected + */ + this._width = 0; + /** + * @type {number} + * @protected + */ + this._height = 0; + /** + * @type {Animator} + * @protected + */ + this._scrollAnimator = null; + /** + * @type {?Object} + */ + this.delegate = null; + /** + * @type {!number} + */ + this._lastTouchPosition = 0; + /** + * @type {!number} + */ + this._lastTouchVelocity = 0; + /** + * @type {!number} + */ + this._lastTouchTimeStamp = 0; + + this.element.addEventListener('mousewheel', this.onMouseWheel, false); + this.element.addEventListener('touchstart', this.onTouchStart, false); + + /** + * The content offset is partitioned so the it can go beyond the CSS limit + * of 33554433px. + * @type {number} + * @protected + */ + this._partitionNumber = 0; +} + +ScrollView.prototype = Object.create(View.prototype); + +ScrollView.PartitionHeight = 100000; +ScrollView.ClassNameScrollView = 'scroll-view'; +ScrollView.ClassNameScrollViewContent = 'scroll-view-content'; + +/** + * @param {!Event} event + */ +ScrollView.prototype.onTouchStart = function(event) { + var touch = event.touches[0]; + this._lastTouchPosition = touch.clientY; + this._lastTouchVelocity = 0; + this._lastTouchTimeStamp = event.timeStamp; + if (this._scrollAnimator) + this._scrollAnimator.stop(); + window.addEventListener('touchmove', this.onWindowTouchMove, false); + window.addEventListener('touchend', this.onWindowTouchEnd, false); +}; + +/** + * @param {!Event} event + */ +ScrollView.prototype.onWindowTouchMove = function(event) { + var touch = event.touches[0]; + var deltaTime = event.timeStamp - this._lastTouchTimeStamp; + var deltaY = this._lastTouchPosition - touch.clientY; + this.scrollBy(deltaY, false); + this._lastTouchVelocity = deltaY / deltaTime; + this._lastTouchPosition = touch.clientY; + this._lastTouchTimeStamp = event.timeStamp; + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * @param {!Event} event + */ +ScrollView.prototype.onWindowTouchEnd = function(event) { + if (Math.abs(this._lastTouchVelocity) > 0.01) { + this._scrollAnimator = new FlingGestureAnimator(this._lastTouchVelocity, this._contentOffset); + this._scrollAnimator.step = this.onFlingGestureAnimatorStep; + this._scrollAnimator.start(); + } + window.removeEventListener('touchmove', this.onWindowTouchMove, false); + window.removeEventListener('touchend', this.onWindowTouchEnd, false); +}; + +/** + * @param {!Animator} animator + */ +ScrollView.prototype.onFlingGestureAnimatorStep = function(animator) { + this.scrollTo(animator.currentValue, false); +}; + +/** + * @return {!Animator} + */ +ScrollView.prototype.scrollAnimator = function() { + return this._scrollAnimator; +}; + +/** + * @param {!number} width + */ +ScrollView.prototype.setWidth = function(width) { + console.assert(isFinite(width)); + if (this._width === width) + return; + this._width = width; + this.element.style.width = this._width + 'px'; +}; + +/** + * @return {!number} + */ +ScrollView.prototype.width = function() { + return this._width; +}; + +/** + * @param {!number} height + */ +ScrollView.prototype.setHeight = function(height) { + console.assert(isFinite(height)); + if (this._height === height) + return; + this._height = height; + this.element.style.height = height + 'px'; + if (this.delegate) + this.delegate.scrollViewDidChangeHeight(this); +}; + +/** + * @return {!number} + */ +ScrollView.prototype.height = function() { + return this._height; +}; + +/** + * @param {!Animator} animator + */ +ScrollView.prototype.onScrollAnimatorStep = function(animator) { + this.setContentOffset(animator.currentValue); +}; + +/** + * @param {!number} offset + * @param {?boolean} animate + */ +ScrollView.prototype.scrollTo = function(offset, animate) { + console.assert(isFinite(offset)); + if (!animate) { + this.setContentOffset(offset); + return; + } + if (this._scrollAnimator) + this._scrollAnimator.stop(); + this._scrollAnimator = new TransitionAnimator(); + this._scrollAnimator.step = this.onScrollAnimatorStep; + this._scrollAnimator.setFrom(this._contentOffset); + this._scrollAnimator.setTo(offset); + this._scrollAnimator.duration = 300; + this._scrollAnimator.start(); +}; + +/** + * @param {!number} offset + * @param {?boolean} animate + */ +ScrollView.prototype.scrollBy = function(offset, animate) { + this.scrollTo(this._contentOffset + offset, animate); +}; + +/** + * @return {!number} + */ +ScrollView.prototype.contentOffset = function() { + return this._contentOffset; +}; + +/** + * @param {?Event} event + */ +ScrollView.prototype.onMouseWheel = function(event) { + this.setContentOffset(this._contentOffset - event.wheelDelta / 30); + event.stopPropagation(); + event.preventDefault(); +}; + + +/** + * @param {!number} value + */ +ScrollView.prototype.setContentOffset = function(value) { + console.assert(isFinite(value)); + value = Math.min(this.maximumContentOffset - this._height, Math.max(this.minimumContentOffset, Math.floor(value))); + if (this._contentOffset === value) + return; + this._contentOffset = value; + this._updateScrollContent(); + if (this.delegate) + this.delegate.scrollViewDidChangeContentOffset(this); +}; + +ScrollView.prototype._updateScrollContent = function() { + var newPartitionNumber = Math.floor(this._contentOffset / ScrollView.PartitionHeight); + var partitionChanged = this._partitionNumber !== newPartitionNumber; + this._partitionNumber = newPartitionNumber; + this.contentElement.style.webkitTransform = + 'translate(0, ' + (-this.contentPositionForContentOffset(this._contentOffset)) + 'px)'; + if (this.delegate && partitionChanged) + this.delegate.scrollViewDidChangePartition(this); +}; + +/** + * @param {!View|Node} parent + * @param {?View|Node=} before + * @override + */ +ScrollView.prototype.attachTo = function(parent, before) { + View.prototype.attachTo.call(this, parent, before); + this._updateScrollContent(); +}; + +/** + * @param {!number} offset + */ +ScrollView.prototype.contentPositionForContentOffset = function(offset) { + return offset - this._partitionNumber * ScrollView.PartitionHeight; +}; + +/** + * @constructor + * @extends View + */ +function ListCell() { + View.call(this, createElement('div', ListCell.ClassNameListCell)); + + /** + * @type {!number} + */ + this.row = NaN; + /** + * @type {!number} + */ + this._width = 0; + /** + * @type {!number} + */ + this._position = 0; +} + +ListCell.prototype = Object.create(View.prototype); + +ListCell.DefaultRecycleBinLimit = 64; +ListCell.ClassNameListCell = 'list-cell'; +ListCell.ClassNameHidden = 'hidden'; + +/** + * @return {!Array} An array to keep thrown away cells. + */ +ListCell.prototype._recycleBin = function() { + console.assert(false, 'NOT REACHED: ListCell.prototype._recycleBin needs to be overridden.'); + return []; +}; + +ListCell.prototype.throwAway = function() { + this.hide(); + var limit = typeof this.constructor.RecycleBinLimit === 'undefined' ? ListCell.DefaultRecycleBinLimit : + this.constructor.RecycleBinLimit; + var recycleBin = this._recycleBin(); + if (recycleBin.length < limit) + recycleBin.push(this); +}; + +ListCell.prototype.show = function() { + this.element.classList.remove(ListCell.ClassNameHidden); +}; + +ListCell.prototype.hide = function() { + this.element.classList.add(ListCell.ClassNameHidden); +}; + +/** + * @return {!number} Width in pixels. + */ +ListCell.prototype.width = function() { + return this._width; +}; + +/** + * @param {!number} width Width in pixels. + */ +ListCell.prototype.setWidth = function(width) { + if (this._width === width) + return; + this._width = width; + this.element.style.width = this._width + 'px'; +}; + +/** + * @return {!number} Position in pixels. + */ +ListCell.prototype.position = function() { + return this._position; +}; + +/** + * @param {!number} y Position in pixels. + */ +ListCell.prototype.setPosition = function(y) { + if (this._position === y) + return; + this._position = y; + this.element.style.webkitTransform = 'translate(0, ' + this._position + 'px)'; +}; + +/** + * @param {!boolean} selected + */ +ListCell.prototype.setSelected = function(selected) { + if (this._selected === selected) + return; + this._selected = selected; + if (this._selected) + this.element.classList.add('selected'); + else + this.element.classList.remove('selected'); +}; + +/** + * @constructor + * @extends View + */ +function ListView() { + View.call(this, createElement('div', ListView.ClassNameListView)); + this.element.tabIndex = 0; + this.element.setAttribute('role', 'grid'); + + /** + * @type {!number} + * @private + */ + this._width = 0; + /** + * @type {!Object} + * @private + */ + this._cells = {}; + + /** + * @type {!number} + */ + this.selectedRow = ListView.NoSelection; + + /** + * @type {!ScrollView} + */ + this.scrollView = new ScrollView(); + this.scrollView.delegate = this; + this.scrollView.minimumContentOffset = 0; + this.scrollView.setWidth(0); + this.scrollView.setHeight(0); + this.scrollView.attachTo(this); + + this.element.addEventListener('click', this.onClick, false); + + /** + * @type {!boolean} + * @private + */ + this._needsUpdateCells = false; +} + +ListView.prototype = Object.create(View.prototype); + +ListView.NoSelection = -1; +ListView.ClassNameListView = 'list-view'; + +ListView.prototype.onAnimationFrameWillFinish = function() { + if (this._needsUpdateCells) + this.updateCells(); +}; + +/** + * @param {!boolean} needsUpdateCells + */ +ListView.prototype.setNeedsUpdateCells = function(needsUpdateCells) { + if (this._needsUpdateCells === needsUpdateCells) + return; + this._needsUpdateCells = needsUpdateCells; + if (this._needsUpdateCells) + AnimationManager.shared.on(AnimationManager.EventTypeAnimationFrameWillFinish, this.onAnimationFrameWillFinish); + else + AnimationManager.shared.removeListener( + AnimationManager.EventTypeAnimationFrameWillFinish, this.onAnimationFrameWillFinish); +}; + +/** + * @param {!number} row + * @return {?ListCell} + */ +ListView.prototype.cellAtRow = function(row) { + return this._cells[row]; +}; + +/** + * @param {!number} offset Scroll offset in pixels. + * @return {!number} + */ +ListView.prototype.rowAtScrollOffset = function(offset) { + console.assert(false, 'NOT REACHED: ListView.prototype.rowAtScrollOffset needs to be overridden.'); + return 0; +}; + +/** + * @param {!number} row + * @return {!number} Scroll offset in pixels. + */ +ListView.prototype.scrollOffsetForRow = function(row) { + console.assert(false, 'NOT REACHED: ListView.prototype.scrollOffsetForRow needs to be overridden.'); + return 0; +}; + +/** + * @param {!number} row + * @return {!ListCell} + */ +ListView.prototype.addCellIfNecessary = function(row) { + var cell = this._cells[row]; + if (cell) + return cell; + cell = this.prepareNewCell(row); + cell.attachTo(this.scrollView.contentElement); + cell.setWidth(this._width); + cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(row))); + this._cells[row] = cell; + return cell; +}; + +/** + * @param {!number} row + * @return {!ListCell} + */ +ListView.prototype.prepareNewCell = function(row) { + console.assert(false, 'NOT REACHED: ListView.prototype.prepareNewCell should be overridden.'); + return new ListCell(); +}; + +/** + * @param {!ListCell} cell + */ +ListView.prototype.throwAwayCell = function(cell) { + delete this._cells[cell.row]; + cell.throwAway(); +}; + +/** + * @return {!number} + */ +ListView.prototype.firstVisibleRow = function() { + return this.rowAtScrollOffset(this.scrollView.contentOffset()); +}; + +/** + * @return {!number} + */ +ListView.prototype.lastVisibleRow = function() { + return this.rowAtScrollOffset(this.scrollView.contentOffset() + this.scrollView.height() - 1); +}; + +/** + * @param {!ScrollView} scrollView + */ +ListView.prototype.scrollViewDidChangeContentOffset = function(scrollView) { + this.setNeedsUpdateCells(true); +}; + +/** + * @param {!ScrollView} scrollView + */ +ListView.prototype.scrollViewDidChangeHeight = function(scrollView) { + this.setNeedsUpdateCells(true); +}; + +/** + * @param {!ScrollView} scrollView + */ +ListView.prototype.scrollViewDidChangePartition = function(scrollView) { + this.setNeedsUpdateCells(true); +}; + +ListView.prototype.updateCells = function() { + var firstVisibleRow = this.firstVisibleRow(); + var lastVisibleRow = this.lastVisibleRow(); + console.assert(firstVisibleRow <= lastVisibleRow); + for (var c in this._cells) { + var cell = this._cells[c]; + if (cell.row < firstVisibleRow || cell.row > lastVisibleRow) + this.throwAwayCell(cell); + } + for (var i = firstVisibleRow; i <= lastVisibleRow; ++i) { + var cell = this._cells[i]; + if (cell) + cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(cell.row))); + else + this.addCellIfNecessary(i); + } + this.setNeedsUpdateCells(false); +}; + +/** + * @return {!number} Width in pixels. + */ +ListView.prototype.width = function() { + return this._width; +}; + +/** + * @param {!number} width Width in pixels. + */ +ListView.prototype.setWidth = function(width) { + if (this._width === width) + return; + this._width = width; + this.scrollView.setWidth(this._width); + for (var c in this._cells) { + this._cells[c].setWidth(this._width); + } + this.element.style.width = this._width + 'px'; + this.setNeedsUpdateCells(true); +}; + +/** + * @return {!number} Height in pixels. + */ +ListView.prototype.height = function() { + return this.scrollView.height(); +}; + +/** + * @param {!number} height Height in pixels. + */ +ListView.prototype.setHeight = function(height) { + this.scrollView.setHeight(height); +}; + +/** + * @param {?Event} event + */ +ListView.prototype.onClick = function(event) { + var clickedCellElement = enclosingNodeOrSelfWithClass(event.target, ListCell.ClassNameListCell); + if (!clickedCellElement) + return; + var clickedCell = clickedCellElement.$view; + if (clickedCell.row !== this.selectedRow) + this.select(clickedCell.row); +}; + +/** + * @param {!number} row + */ +ListView.prototype.select = function(row) { + if (this.selectedRow === row) + return; + this.deselect(); + if (row === ListView.NoSelection) + return; + this.selectedRow = row; + var selectedCell = this._cells[this.selectedRow]; + if (selectedCell) + selectedCell.setSelected(true); +}; + +ListView.prototype.deselect = function() { + if (this.selectedRow === ListView.NoSelection) + return; + var selectedCell = this._cells[this.selectedRow]; + if (selectedCell) + selectedCell.setSelected(false); + this.selectedRow = ListView.NoSelection; +}; + +/** + * @param {!number} row + * @param {!boolean} animate + */ +ListView.prototype.scrollToRow = function(row, animate) { + this.scrollView.scrollTo(this.scrollOffsetForRow(row), animate); +}; + +/** + * @constructor + * @extends View + * @param {!ScrollView} scrollView + */ +function ScrubbyScrollBar(scrollView) { + View.call(this, createElement('div', ScrubbyScrollBar.ClassNameScrubbyScrollBar)); + + /** + * @type {!Element} + * @const + */ + this.thumb = createElement('div', ScrubbyScrollBar.ClassNameScrubbyScrollThumb); + this.element.appendChild(this.thumb); + + /** + * @type {!ScrollView} + * @const + */ + this.scrollView = scrollView; + + /** + * @type {!number} + * @protected + */ + this._height = 0; + /** + * @type {!number} + * @protected + */ + this._thumbHeight = 0; + /** + * @type {!number} + * @protected + */ + this._thumbPosition = 0; + + this.setHeight(0); + this.setThumbHeight(ScrubbyScrollBar.ThumbHeight); + + /** + * @type {?Animator} + * @protected + */ + this._thumbStyleTopAnimator = null; + + /** + * @type {?number} + * @protected + */ + this._timer = null; + + this.element.addEventListener('mousedown', this.onMouseDown, false); + this.element.addEventListener('touchstart', this.onTouchStart, false); +} + +ScrubbyScrollBar.prototype = Object.create(View.prototype); + +ScrubbyScrollBar.ScrollInterval = 16; +ScrubbyScrollBar.ThumbMargin = 2; +ScrubbyScrollBar.ThumbHeight = 30; +ScrubbyScrollBar.ClassNameScrubbyScrollBar = 'scrubby-scroll-bar'; +ScrubbyScrollBar.ClassNameScrubbyScrollThumb = 'scrubby-scroll-thumb'; + +/** + * @param {?Event} event + */ +ScrubbyScrollBar.prototype.onTouchStart = function(event) { + var touch = event.touches[0]; + this._setThumbPositionFromEventPosition(touch.clientY); + if (this._thumbStyleTopAnimator) + this._thumbStyleTopAnimator.stop(); + this._timer = setInterval(this.onScrollTimer, ScrubbyScrollBar.ScrollInterval); + window.addEventListener('touchmove', this.onWindowTouchMove, false); + window.addEventListener('touchend', this.onWindowTouchEnd, false); + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * @param {?Event} event + */ +ScrubbyScrollBar.prototype.onWindowTouchMove = function(event) { + var touch = event.touches[0]; + this._setThumbPositionFromEventPosition(touch.clientY); + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * @param {?Event} event + */ +ScrubbyScrollBar.prototype.onWindowTouchEnd = function(event) { + this._thumbStyleTopAnimator = new TransitionAnimator(); + this._thumbStyleTopAnimator.step = this.onThumbStyleTopAnimationStep; + this._thumbStyleTopAnimator.setFrom(this.thumb.offsetTop); + this._thumbStyleTopAnimator.setTo((this._height - this._thumbHeight) / 2); + this._thumbStyleTopAnimator.timingFunction = AnimationTimingFunction.EaseInOut; + this._thumbStyleTopAnimator.duration = 100; + this._thumbStyleTopAnimator.start(); + + window.removeEventListener('touchmove', this.onWindowTouchMove, false); + window.removeEventListener('touchend', this.onWindowTouchEnd, false); + clearInterval(this._timer); +}; + +/** + * @return {!number} Height of the view in pixels. + */ +ScrubbyScrollBar.prototype.height = function() { + return this._height; +}; + +/** + * @param {!number} height Height of the view in pixels. + */ +ScrubbyScrollBar.prototype.setHeight = function(height) { + if (this._height === height) + return; + this._height = height; + this.element.style.height = this._height + 'px'; + this.thumb.style.top = ((this._height - this._thumbHeight) / 2) + 'px'; + this._thumbPosition = 0; +}; + +/** + * @param {!number} height Height of the scroll bar thumb in pixels. + */ +ScrubbyScrollBar.prototype.setThumbHeight = function(height) { + if (this._thumbHeight === height) + return; + this._thumbHeight = height; + this.thumb.style.height = this._thumbHeight + 'px'; + this.thumb.style.top = ((this._height - this._thumbHeight) / 2) + 'px'; + this._thumbPosition = 0; +}; + +/** + * @param {number} position + */ +ScrubbyScrollBar.prototype._setThumbPositionFromEventPosition = function(position) { + var thumbMin = ScrubbyScrollBar.ThumbMargin; + var thumbMax = this._height - this._thumbHeight - ScrubbyScrollBar.ThumbMargin * 2; + var y = position - this.element.getBoundingClientRect().top - this.element.clientTop + this.element.scrollTop; + var thumbPosition = y - this._thumbHeight / 2; + thumbPosition = Math.max(thumbPosition, thumbMin); + thumbPosition = Math.min(thumbPosition, thumbMax); + this.thumb.style.top = thumbPosition + 'px'; + this._thumbPosition = 1.0 - (thumbPosition - thumbMin) / (thumbMax - thumbMin) * 2; +}; + +/** + * @param {?Event} event + */ +ScrubbyScrollBar.prototype.onMouseDown = function(event) { + this._setThumbPositionFromEventPosition(event.clientY); + + window.addEventListener('mousemove', this.onWindowMouseMove, false); + window.addEventListener('mouseup', this.onWindowMouseUp, false); + if (this._thumbStyleTopAnimator) + this._thumbStyleTopAnimator.stop(); + this._timer = setInterval(this.onScrollTimer, ScrubbyScrollBar.ScrollInterval); + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * @param {?Event} event + */ +ScrubbyScrollBar.prototype.onWindowMouseMove = function(event) { + this._setThumbPositionFromEventPosition(event.clientY); +}; + +/** + * @param {?Event} event + */ +ScrubbyScrollBar.prototype.onWindowMouseUp = function(event) { + this._thumbStyleTopAnimator = new TransitionAnimator(); + this._thumbStyleTopAnimator.step = this.onThumbStyleTopAnimationStep; + this._thumbStyleTopAnimator.setFrom(this.thumb.offsetTop); + this._thumbStyleTopAnimator.setTo((this._height - this._thumbHeight) / 2); + this._thumbStyleTopAnimator.timingFunction = AnimationTimingFunction.EaseInOut; + this._thumbStyleTopAnimator.duration = 100; + this._thumbStyleTopAnimator.start(); + + window.removeEventListener('mousemove', this.onWindowMouseMove, false); + window.removeEventListener('mouseup', this.onWindowMouseUp, false); + clearInterval(this._timer); +}; + +/** + * @param {!Animator} animator + */ +ScrubbyScrollBar.prototype.onThumbStyleTopAnimationStep = function(animator) { + this.thumb.style.top = animator.currentValue + 'px'; +}; + +ScrubbyScrollBar.prototype.onScrollTimer = function() { + var scrollAmount = Math.pow(this._thumbPosition, 2) * 10; + if (this._thumbPosition > 0) + scrollAmount = -scrollAmount; + this.scrollView.scrollBy(scrollAmount, false); +}; + +/** + * @constructor + * @extends ListCell + * @param {!Array} shortMonthLabels + */ +function YearListCell(shortMonthLabels) { + ListCell.call(this); + this.element.classList.add(YearListCell.ClassNameYearListCell); + this.element.style.height = YearListCell.Height + 'px'; + + /** + * @type {!Element} + * @const + */ + this.label = createElement('div', YearListCell.ClassNameLabel, '----'); + this.element.appendChild(this.label); + this.label.style.height = (YearListCell.Height - YearListCell.BorderBottomWidth) + 'px'; + this.label.style.lineHeight = (YearListCell.Height - YearListCell.BorderBottomWidth) + 'px'; + + /** + * @type {!Array} Array of the 12 month button elements. + * @const + */ + this.monthButtons = []; + var monthChooserElement = createElement('div', YearListCell.ClassNameMonthChooser); + for (var r = 0; r < YearListCell.ButtonRows; ++r) { + var buttonsRow = createElement('div', YearListCell.ClassNameMonthButtonsRow); + buttonsRow.setAttribute('role', 'row'); + for (var c = 0; c < YearListCell.ButtonColumns; ++c) { + var month = c + r * YearListCell.ButtonColumns; + var button = createElement('div', YearListCell.ClassNameMonthButton, shortMonthLabels[month]); + button.setAttribute('role', 'gridcell'); + button.dataset.month = month; + buttonsRow.appendChild(button); + this.monthButtons.push(button); + } + monthChooserElement.appendChild(buttonsRow); + } + this.element.appendChild(monthChooserElement); + + /** + * @type {!boolean} + * @private + */ + this._selected = false; + /** + * @type {!number} + * @private + */ + this._height = 0; +} + +YearListCell.prototype = Object.create(ListCell.prototype); + +YearListCell.Height = hasInaccuratePointingDevice() ? 31 : 25; +YearListCell.BorderBottomWidth = 1; +YearListCell.ButtonRows = 3; +YearListCell.ButtonColumns = 4; +YearListCell.SelectedHeight = hasInaccuratePointingDevice() ? 127 : 121; +YearListCell.ClassNameYearListCell = 'year-list-cell'; +YearListCell.ClassNameLabel = 'label'; +YearListCell.ClassNameMonthChooser = 'month-chooser'; +YearListCell.ClassNameMonthButtonsRow = 'month-buttons-row'; +YearListCell.ClassNameMonthButton = 'month-button'; +YearListCell.ClassNameHighlighted = 'highlighted'; + +YearListCell._recycleBin = []; + +/** + * @return {!Array} + * @override + */ +YearListCell.prototype._recycleBin = function() { + return YearListCell._recycleBin; +}; + +/** + * @param {!number} row + */ +YearListCell.prototype.reset = function(row) { + this.row = row; + this.label.textContent = row + 1; + for (var i = 0; i < this.monthButtons.length; ++i) { + this.monthButtons[i].classList.remove(YearListCell.ClassNameHighlighted); + } + this.show(); +}; + +/** + * @return {!number} The height in pixels. + */ +YearListCell.prototype.height = function() { + return this._height; +}; + +/** + * @param {!number} height Height in pixels. + */ +YearListCell.prototype.setHeight = function(height) { + if (this._height === height) + return; + this._height = height; + this.element.style.height = this._height + 'px'; +}; + +/** + * @constructor + * @extends ListView + * @param {!Month} minimumMonth + * @param {!Month} maximumMonth + */ +function YearListView(minimumMonth, maximumMonth) { + ListView.call(this); + this.element.classList.add('year-list-view'); + + /** + * @type {?Month} + */ + this.highlightedMonth = null; + /** + * @type {!Month} + * @const + * @protected + */ + this._minimumMonth = minimumMonth; + /** + * @type {!Month} + * @const + * @protected + */ + this._maximumMonth = maximumMonth; + + this.scrollView.minimumContentOffset = (this._minimumMonth.year - 1) * YearListCell.Height; + this.scrollView.maximumContentOffset = + (this._maximumMonth.year - 1) * YearListCell.Height + YearListCell.SelectedHeight; + + /** + * @type {!Object} + * @const + * @protected + */ + this._runningAnimators = {}; + /** + * @type {!Array} + * @const + * @protected + */ + this._animatingRows = []; + /** + * @type {!boolean} + * @protected + */ + this._ignoreMouseOutUntillNextMouseOver = false; + + /** + * @type {!ScrubbyScrollBar} + * @const + */ + this.scrubbyScrollBar = new ScrubbyScrollBar(this.scrollView); + this.scrubbyScrollBar.attachTo(this); + + this.element.addEventListener('mouseover', this.onMouseOver, false); + this.element.addEventListener('mouseout', this.onMouseOut, false); + this.element.addEventListener('keydown', this.onKeyDown, false); + this.element.addEventListener('touchstart', this.onTouchStart, false); +} + +YearListView.prototype = Object.create(ListView.prototype); + +YearListView.Height = YearListCell.SelectedHeight - 1; +YearListView.EventTypeYearListViewDidHide = 'yearListViewDidHide'; +YearListView.EventTypeYearListViewDidSelectMonth = 'yearListViewDidSelectMonth'; + +/** + * @param {?Event} event + */ +YearListView.prototype.onTouchStart = function(event) { + var touch = event.touches[0]; + var monthButtonElement = enclosingNodeOrSelfWithClass(touch.target, YearListCell.ClassNameMonthButton); + if (!monthButtonElement) + return; + var cellElement = enclosingNodeOrSelfWithClass(monthButtonElement, YearListCell.ClassNameYearListCell); + var cell = cellElement.$view; + this.highlightMonth(new Month(cell.row + 1, parseInt(monthButtonElement.dataset.month, 10))); +}; + +/** + * @param {?Event} event + */ +YearListView.prototype.onMouseOver = function(event) { + var monthButtonElement = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton); + if (!monthButtonElement) + return; + var cellElement = enclosingNodeOrSelfWithClass(monthButtonElement, YearListCell.ClassNameYearListCell); + var cell = cellElement.$view; + this.highlightMonth(new Month(cell.row + 1, parseInt(monthButtonElement.dataset.month, 10))); + this._ignoreMouseOutUntillNextMouseOver = false; +}; + +/** + * @param {?Event} event + */ +YearListView.prototype.onMouseOut = function(event) { + if (this._ignoreMouseOutUntillNextMouseOver) + return; + var monthButtonElement = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton); + if (!monthButtonElement) { + this.dehighlightMonth(); + } +}; + +/** + * @param {!number} width Width in pixels. + * @override + */ +YearListView.prototype.setWidth = function(width) { + ListView.prototype.setWidth.call(this, width - this.scrubbyScrollBar.element.offsetWidth); + this.element.style.width = width + 'px'; +}; + +/** + * @param {!number} height Height in pixels. + * @override + */ +YearListView.prototype.setHeight = function(height) { + ListView.prototype.setHeight.call(this, height); + this.scrubbyScrollBar.setHeight(height); +}; + +/** + * @enum {number} + */ +YearListView.RowAnimationDirection = { + Opening: 0, + Closing: 1 +}; + +/** + * @param {!number} row + * @param {!YearListView.RowAnimationDirection} direction + */ +YearListView.prototype._animateRow = function(row, direction) { + var fromValue = + direction === YearListView.RowAnimationDirection.Closing ? YearListCell.SelectedHeight : YearListCell.Height; + var oldAnimator = this._runningAnimators[row]; + if (oldAnimator) { + oldAnimator.stop(); + fromValue = oldAnimator.currentValue; + } + var cell = this.cellAtRow(row); + var animator = new TransitionAnimator(); + animator.step = this.onCellHeightAnimatorStep; + animator.setFrom(fromValue); + animator.setTo( + direction === YearListView.RowAnimationDirection.Opening ? YearListCell.SelectedHeight : YearListCell.Height); + animator.timingFunction = AnimationTimingFunction.EaseInOut; + animator.duration = 300; + animator.row = row; + animator.on(Animator.EventTypeDidAnimationStop, this.onCellHeightAnimatorDidStop); + this._runningAnimators[row] = animator; + this._animatingRows.push(row); + this._animatingRows.sort(); + animator.start(); +}; + +/** + * @param {?Animator} animator + */ +YearListView.prototype.onCellHeightAnimatorDidStop = function(animator) { + delete this._runningAnimators[animator.row]; + var index = this._animatingRows.indexOf(animator.row); + this._animatingRows.splice(index, 1); +}; + +/** + * @param {!Animator} animator + */ +YearListView.prototype.onCellHeightAnimatorStep = function(animator) { + var cell = this.cellAtRow(animator.row); + if (cell) + cell.setHeight(animator.currentValue); + this.updateCells(); +}; + +/** + * @param {?Event} event + */ +YearListView.prototype.onClick = function(event) { + var oldSelectedRow = this.selectedRow; + ListView.prototype.onClick.call(this, event); + var year = this.selectedRow + 1; + if (this.selectedRow !== oldSelectedRow) { + var month = this.highlightedMonth ? this.highlightedMonth.month : 0; + this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month)); + this.scrollView.scrollTo(this.selectedRow * YearListCell.Height, true); + } else { + var monthButton = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton); + if (!monthButton || monthButton.getAttribute('aria-disabled') == 'true') + return; + var month = parseInt(monthButton.dataset.month, 10); + this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month)); + this.hide(); + } +}; + +/** + * @param {!number} scrollOffset + * @return {!number} + * @override + */ +YearListView.prototype.rowAtScrollOffset = function(scrollOffset) { + var remainingOffset = scrollOffset; + var lastAnimatingRow = 0; + var rowsWithIrregularHeight = this._animatingRows.slice(); + if (this.selectedRow > -1 && !this._runningAnimators[this.selectedRow]) { + rowsWithIrregularHeight.push(this.selectedRow); + rowsWithIrregularHeight.sort(); + } + for (var i = 0; i < rowsWithIrregularHeight.length; ++i) { + var row = rowsWithIrregularHeight[i]; + var animator = this._runningAnimators[row]; + var rowHeight = animator ? animator.currentValue : YearListCell.SelectedHeight; + if (remainingOffset <= (row - lastAnimatingRow) * YearListCell.Height) { + return lastAnimatingRow + Math.floor(remainingOffset / YearListCell.Height); + } + remainingOffset -= (row - lastAnimatingRow) * YearListCell.Height; + if (remainingOffset <= (rowHeight - YearListCell.Height)) + return row; + remainingOffset -= rowHeight - YearListCell.Height; + lastAnimatingRow = row; + } + return lastAnimatingRow + Math.floor(remainingOffset / YearListCell.Height); +}; + +/** + * @param {!number} row + * @return {!number} + * @override + */ +YearListView.prototype.scrollOffsetForRow = function(row) { + var scrollOffset = row * YearListCell.Height; + for (var i = 0; i < this._animatingRows.length; ++i) { + var animatingRow = this._animatingRows[i]; + if (animatingRow >= row) + break; + var animator = this._runningAnimators[animatingRow]; + scrollOffset += animator.currentValue - YearListCell.Height; + } + if (this.selectedRow > -1 && this.selectedRow < row && !this._runningAnimators[this.selectedRow]) { + scrollOffset += YearListCell.SelectedHeight - YearListCell.Height; + } + return scrollOffset; +}; + +/** + * @param {!number} row + * @return {!YearListCell} + * @override + */ +YearListView.prototype.prepareNewCell = function(row) { + var cell = YearListCell._recycleBin.pop() || new YearListCell(global.params.shortMonthLabels); + cell.reset(row); + cell.setSelected(this.selectedRow === row); + for (var i = 0; i < cell.monthButtons.length; ++i) { + var month = new Month(row + 1, i); + cell.monthButtons[i].id = month.toString(); + cell.monthButtons[i].setAttribute( + 'aria-disabled', this._minimumMonth > month || this._maximumMonth < month ? 'true' : 'false'); + cell.monthButtons[i].setAttribute('aria-label', month.toLocaleString()); + } + if (this.highlightedMonth && row === this.highlightedMonth.year - 1) { + var monthButton = cell.monthButtons[this.highlightedMonth.month]; + monthButton.classList.add(YearListCell.ClassNameHighlighted); + // aria-activedescendant assumes both elements have layoutObjects, and + // |monthButton| might have no layoutObject yet. + var element = this.element; + setTimeout(function() { + element.setAttribute('aria-activedescendant', monthButton.id); + }, 0); + } + var animator = this._runningAnimators[row]; + if (animator) + cell.setHeight(animator.currentValue); + else if (row === this.selectedRow) + cell.setHeight(YearListCell.SelectedHeight); + else + cell.setHeight(YearListCell.Height); + return cell; +}; + +/** + * @override + */ +YearListView.prototype.updateCells = function() { + var firstVisibleRow = this.firstVisibleRow(); + var lastVisibleRow = this.lastVisibleRow(); + console.assert(firstVisibleRow <= lastVisibleRow); + for (var c in this._cells) { + var cell = this._cells[c]; + if (cell.row < firstVisibleRow || cell.row > lastVisibleRow) + this.throwAwayCell(cell); + } + for (var i = firstVisibleRow; i <= lastVisibleRow; ++i) { + var cell = this._cells[i]; + if (cell) + cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(cell.row))); + else + this.addCellIfNecessary(i); + } + this.setNeedsUpdateCells(false); +}; + +/** + * @override + */ +YearListView.prototype.deselect = function() { + if (this.selectedRow === ListView.NoSelection) + return; + var selectedCell = this._cells[this.selectedRow]; + if (selectedCell) + selectedCell.setSelected(false); + this._animateRow(this.selectedRow, YearListView.RowAnimationDirection.Closing); + this.selectedRow = ListView.NoSelection; + this.setNeedsUpdateCells(true); +}; + +YearListView.prototype.deselectWithoutAnimating = function() { + if (this.selectedRow === ListView.NoSelection) + return; + var selectedCell = this._cells[this.selectedRow]; + if (selectedCell) { + selectedCell.setSelected(false); + selectedCell.setHeight(YearListCell.Height); + } + this.selectedRow = ListView.NoSelection; + this.setNeedsUpdateCells(true); +}; + +/** + * @param {!number} row + * @override + */ +YearListView.prototype.select = function(row) { + if (this.selectedRow === row) + return; + this.deselect(); + if (row === ListView.NoSelection) + return; + this.selectedRow = row; + if (this.selectedRow !== ListView.NoSelection) { + var selectedCell = this._cells[this.selectedRow]; + this._animateRow(this.selectedRow, YearListView.RowAnimationDirection.Opening); + if (selectedCell) + selectedCell.setSelected(true); + var month = this.highlightedMonth ? this.highlightedMonth.month : 0; + this.highlightMonth(new Month(this.selectedRow + 1, month)); + } + this.setNeedsUpdateCells(true); +}; + +/** + * @param {!number} row + */ +YearListView.prototype.selectWithoutAnimating = function(row) { + if (this.selectedRow === row) + return; + this.deselectWithoutAnimating(); + if (row === ListView.NoSelection) + return; + this.selectedRow = row; + if (this.selectedRow !== ListView.NoSelection) { + var selectedCell = this._cells[this.selectedRow]; + if (selectedCell) { + selectedCell.setSelected(true); + selectedCell.setHeight(YearListCell.SelectedHeight); + } + var month = this.highlightedMonth ? this.highlightedMonth.month : 0; + this.highlightMonth(new Month(this.selectedRow + 1, month)); + } + this.setNeedsUpdateCells(true); +}; + +/** + * @param {!Month} month + * @return {?HTMLDivElement} + */ +YearListView.prototype.buttonForMonth = function(month) { + if (!month) + return null; + var row = month.year - 1; + var cell = this.cellAtRow(row); + if (!cell) + return null; + return cell.monthButtons[month.month]; +}; + +YearListView.prototype.dehighlightMonth = function() { + if (!this.highlightedMonth) + return; + var monthButton = this.buttonForMonth(this.highlightedMonth); + if (monthButton) { + monthButton.classList.remove(YearListCell.ClassNameHighlighted); + } + this.highlightedMonth = null; + this.element.removeAttribute('aria-activedescendant'); +}; + +/** + * @param {!Month} month + */ +YearListView.prototype.highlightMonth = function(month) { + if (this.highlightedMonth && this.highlightedMonth.equals(month)) + return; + this.dehighlightMonth(); + this.highlightedMonth = month; + if (!this.highlightedMonth) + return; + var monthButton = this.buttonForMonth(this.highlightedMonth); + if (monthButton) { + monthButton.classList.add(YearListCell.ClassNameHighlighted); + this.element.setAttribute('aria-activedescendant', monthButton.id); + } +}; + +/** + * @param {!Month} month + */ +YearListView.prototype.show = function(month) { + this._ignoreMouseOutUntillNextMouseOver = true; + + this.scrollToRow(month.year - 1, false); + this.selectWithoutAnimating(month.year - 1); + this.highlightMonth(month); +}; + +YearListView.prototype.hide = function() { + this.dispatchEvent(YearListView.EventTypeYearListViewDidHide, this); +}; + +/** + * @param {!Month} month + */ +YearListView.prototype._moveHighlightTo = function(month) { + this.highlightMonth(month); + this.select(this.highlightedMonth.year - 1); + + this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, month); + this.scrollView.scrollTo(this.selectedRow * YearListCell.Height, true); + return true; +}; + +/** + * @param {?Event} event + */ +YearListView.prototype.onKeyDown = function(event) { + var key = event.key; + var eventHandled = false; + if (key == 't') + eventHandled = this._moveHighlightTo(Month.createFromToday()); + else if (this.highlightedMonth) { + if (global.params.isLocaleRTL ? key == 'ArrowRight' : key == 'ArrowLeft') + eventHandled = this._moveHighlightTo(this.highlightedMonth.previous()); + else if (key == 'ArrowUp') + eventHandled = this._moveHighlightTo(this.highlightedMonth.previous(YearListCell.ButtonColumns)); + else if (global.params.isLocaleRTL ? key == 'ArrowLeft' : key == 'ArrowRight') + eventHandled = this._moveHighlightTo(this.highlightedMonth.next()); + else if (key == 'ArrowDown') + eventHandled = this._moveHighlightTo(this.highlightedMonth.next(YearListCell.ButtonColumns)); + else if (key == 'PageUp') + eventHandled = this._moveHighlightTo(this.highlightedMonth.previous(MonthsPerYear)); + else if (key == 'PageDown') + eventHandled = this._moveHighlightTo(this.highlightedMonth.next(MonthsPerYear)); + else if (key == 'Enter') { + this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, this.highlightedMonth); + this.hide(); + eventHandled = true; + } + } else if (key == 'ArrowUp') { + this.scrollView.scrollBy(-YearListCell.Height, true); + eventHandled = true; + } else if (key == 'ArrowDown') { + this.scrollView.scrollBy(YearListCell.Height, true); + eventHandled = true; + } else if (key == 'PageUp') { + this.scrollView.scrollBy(-this.scrollView.height(), true); + eventHandled = true; + } else if (key == 'PageDown') { + this.scrollView.scrollBy(this.scrollView.height(), true); + eventHandled = true; + } + + if (eventHandled) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +/** + * @constructor + * @extends View + * @param {!Month} minimumMonth + * @param {!Month} maximumMonth + */ +function MonthPopupView(minimumMonth, maximumMonth) { + View.call(this, createElement('div', MonthPopupView.ClassNameMonthPopupView)); + + /** + * @type {!YearListView} + * @const + */ + this.yearListView = new YearListView(minimumMonth, maximumMonth); + this.yearListView.attachTo(this); + + /** + * @type {!boolean} + */ + this.isVisible = false; + + this.element.addEventListener('click', this.onClick, false); +} + +MonthPopupView.prototype = Object.create(View.prototype); + +MonthPopupView.ClassNameMonthPopupView = 'month-popup-view'; + +MonthPopupView.prototype.show = function(initialMonth, calendarTableRect) { + this.isVisible = true; + document.body.appendChild(this.element); + this.yearListView.setWidth(calendarTableRect.width - 2); + this.yearListView.setHeight(YearListView.Height); + if (global.params.isLocaleRTL) + this.yearListView.element.style.right = calendarTableRect.x + 'px'; + else + this.yearListView.element.style.left = calendarTableRect.x + 'px'; + this.yearListView.element.style.top = calendarTableRect.y + 'px'; + this.yearListView.show(initialMonth); + this.yearListView.element.focus(); +}; + +MonthPopupView.prototype.hide = function() { + if (!this.isVisible) + return; + this.isVisible = false; + this.element.parentNode.removeChild(this.element); + this.yearListView.hide(); +}; + +/** + * @param {?Event} event + */ +MonthPopupView.prototype.onClick = function(event) { + if (event.target !== this.element) + return; + this.hide(); +}; + +/** + * @constructor + * @extends View + * @param {!number} maxWidth Maximum width in pixels. + */ +function MonthPopupButton(maxWidth) { + View.call(this, createElement('button', MonthPopupButton.ClassNameMonthPopupButton)); + this.element.setAttribute('aria-label', global.params.axShowMonthSelector); + + /** + * @type {!Element} + * @const + */ + this.labelElement = createElement('span', MonthPopupButton.ClassNameMonthPopupButtonLabel, '-----'); + this.element.appendChild(this.labelElement); + + /** + * @type {!Element} + * @const + */ + this.disclosureTriangleIcon = createElement('span', MonthPopupButton.ClassNameDisclosureTriangle); + this.disclosureTriangleIcon.innerHTML = + '<svg width=\'7\' height=\'5\'><polygon points=\'0,1 7,1 3.5,5\' style=\'fill:#000000;\' /></svg>'; + this.element.appendChild(this.disclosureTriangleIcon); + + /** + * @type {!boolean} + * @protected + */ + this._useShortMonth = this._shouldUseShortMonth(maxWidth); + this.element.style.maxWidth = maxWidth + 'px'; + + this.element.addEventListener('click', this.onClick, false); +} + +MonthPopupButton.prototype = Object.create(View.prototype); + +MonthPopupButton.ClassNameMonthPopupButton = 'month-popup-button'; +MonthPopupButton.ClassNameMonthPopupButtonLabel = 'month-popup-button-label'; +MonthPopupButton.ClassNameDisclosureTriangle = 'disclosure-triangle'; +MonthPopupButton.EventTypeButtonClick = 'buttonClick'; + +/** + * @param {!number} maxWidth Maximum available width in pixels. + * @return {!boolean} + */ +MonthPopupButton.prototype._shouldUseShortMonth = function(maxWidth) { + document.body.appendChild(this.element); + var month = Month.Maximum; + for (var i = 0; i < MonthsPerYear; ++i) { + this.labelElement.textContent = month.toLocaleString(); + if (this.element.offsetWidth > maxWidth) + return true; + month = month.previous(); + } + document.body.removeChild(this.element); + return false; +}; + +/** + * @param {!Month} month + */ +MonthPopupButton.prototype.setCurrentMonth = function(month) { + this.labelElement.textContent = this._useShortMonth ? month.toShortLocaleString() : month.toLocaleString(); +}; + +/** + * @param {?Event} event + */ +MonthPopupButton.prototype.onClick = function(event) { + this.dispatchEvent(MonthPopupButton.EventTypeButtonClick, this); +}; + +/** + * @constructor + * @extends View + */ +function CalendarNavigationButton() { + View.call(this, createElement('button', CalendarNavigationButton.ClassNameCalendarNavigationButton)); + /** + * @type {number} Threshold for starting repeating clicks in milliseconds. + */ + this.repeatingClicksStartingThreshold = CalendarNavigationButton.DefaultRepeatingClicksStartingThreshold; + /** + * @type {number} Interval between reapeating clicks in milliseconds. + */ + this.reapeatingClicksInterval = CalendarNavigationButton.DefaultRepeatingClicksInterval; + /** + * @type {?number} The ID for the timeout that triggers the repeating clicks. + */ + this._timer = null; + this.element.addEventListener('click', this.onClick, false); + this.element.addEventListener('mousedown', this.onMouseDown, false); + this.element.addEventListener('touchstart', this.onTouchStart, false); +}; + +CalendarNavigationButton.prototype = Object.create(View.prototype); + +CalendarNavigationButton.DefaultRepeatingClicksStartingThreshold = 600; +CalendarNavigationButton.DefaultRepeatingClicksInterval = 300; +CalendarNavigationButton.LeftMargin = 4; +CalendarNavigationButton.Width = 24; +CalendarNavigationButton.ClassNameCalendarNavigationButton = 'calendar-navigation-button'; +CalendarNavigationButton.EventTypeButtonClick = 'buttonClick'; +CalendarNavigationButton.EventTypeRepeatingButtonClick = 'repeatingButtonClick'; + +/** + * @param {!boolean} disabled + */ +CalendarNavigationButton.prototype.setDisabled = function(disabled) { + this.element.disabled = disabled; +}; + +/** + * @param {?Event} event + */ +CalendarNavigationButton.prototype.onClick = function(event) { + this.dispatchEvent(CalendarNavigationButton.EventTypeButtonClick, this); +}; + +/** + * @param {?Event} event + */ +CalendarNavigationButton.prototype.onTouchStart = function(event) { + if (this._timer !== null) + return; + this._timer = setTimeout(this.onRepeatingClick, this.repeatingClicksStartingThreshold); + window.addEventListener('touchend', this.onWindowTouchEnd, false); +}; + +/** + * @param {?Event} event + */ +CalendarNavigationButton.prototype.onWindowTouchEnd = function(event) { + if (this._timer === null) + return; + clearTimeout(this._timer); + this._timer = null; + window.removeEventListener('touchend', this.onWindowMouseUp, false); +}; + +/** + * @param {?Event} event + */ +CalendarNavigationButton.prototype.onMouseDown = function(event) { + if (this._timer !== null) + return; + this._timer = setTimeout(this.onRepeatingClick, this.repeatingClicksStartingThreshold); + window.addEventListener('mouseup', this.onWindowMouseUp, false); +}; + +/** + * @param {?Event} event + */ +CalendarNavigationButton.prototype.onWindowMouseUp = function(event) { + if (this._timer === null) + return; + clearTimeout(this._timer); + this._timer = null; + window.removeEventListener('mouseup', this.onWindowMouseUp, false); +}; + +/** + * @param {?Event} event + */ +CalendarNavigationButton.prototype.onRepeatingClick = function(event) { + this.dispatchEvent(CalendarNavigationButton.EventTypeRepeatingButtonClick, this); + this._timer = setTimeout(this.onRepeatingClick, this.reapeatingClicksInterval); +}; + +/** + * @constructor + * @extends View + * @param {!CalendarPicker} calendarPicker + */ +function CalendarHeaderView(calendarPicker) { + View.call(this, createElement('div', CalendarHeaderView.ClassNameCalendarHeaderView)); + this.calendarPicker = calendarPicker; + this.calendarPicker.on(CalendarPicker.EventTypeCurrentMonthChanged, this.onCurrentMonthChanged); + + var titleElement = createElement('div', CalendarHeaderView.ClassNameCalendarTitle); + this.element.appendChild(titleElement); + + /** + * @type {!MonthPopupButton} + */ + this.monthPopupButton = new MonthPopupButton( + this.calendarPicker.calendarTableView.width() - CalendarTableView.BorderWidth * 2 - + CalendarNavigationButton.Width * 3 - CalendarNavigationButton.LeftMargin * 2); + this.monthPopupButton.attachTo(titleElement); + + /** + * @type {!CalendarNavigationButton} + * @const + */ + this._previousMonthButton = new CalendarNavigationButton(); + this._previousMonthButton.attachTo(this); + this._previousMonthButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick); + this._previousMonthButton.on(CalendarNavigationButton.EventTypeRepeatingButtonClick, this.onNavigationButtonClick); + this._previousMonthButton.element.setAttribute('aria-label', global.params.axShowPreviousMonth); + + /** + * @type {!CalendarNavigationButton} + * @const + */ + this._todayButton = new CalendarNavigationButton(); + this._todayButton.attachTo(this); + this._todayButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick); + this._todayButton.element.classList.add(CalendarHeaderView.ClassNameTodayButton); + var monthContainingToday = Month.createFromToday(); + this._todayButton.setDisabled( + monthContainingToday < this.calendarPicker.minimumMonth || + monthContainingToday > this.calendarPicker.maximumMonth); + this._todayButton.element.setAttribute('aria-label', global.params.todayLabel); + + /** + * @type {!CalendarNavigationButton} + * @const + */ + this._nextMonthButton = new CalendarNavigationButton(); + this._nextMonthButton.attachTo(this); + this._nextMonthButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick); + this._nextMonthButton.on(CalendarNavigationButton.EventTypeRepeatingButtonClick, this.onNavigationButtonClick); + this._nextMonthButton.element.setAttribute('aria-label', global.params.axShowNextMonth); + + if (global.params.isLocaleRTL) { + this._nextMonthButton.element.innerHTML = CalendarHeaderView._BackwardTriangle; + this._previousMonthButton.element.innerHTML = CalendarHeaderView._ForwardTriangle; + } else { + this._nextMonthButton.element.innerHTML = CalendarHeaderView._ForwardTriangle; + this._previousMonthButton.element.innerHTML = CalendarHeaderView._BackwardTriangle; + } +} + +CalendarHeaderView.prototype = Object.create(View.prototype); + +CalendarHeaderView.Height = 24; +CalendarHeaderView.BottomMargin = 10; +CalendarHeaderView._ForwardTriangle = + '<svg width=\'4\' height=\'7\'><polygon points=\'0,7 0,0, 4,3.5\' style=\'fill:#6e6e6e;\' /></svg>'; +CalendarHeaderView._BackwardTriangle = + '<svg width=\'4\' height=\'7\'><polygon points=\'0,3.5 4,7 4,0\' style=\'fill:#6e6e6e;\' /></svg>'; +CalendarHeaderView.ClassNameCalendarHeaderView = 'calendar-header-view'; +CalendarHeaderView.ClassNameCalendarTitle = 'calendar-title'; +CalendarHeaderView.ClassNameTodayButton = 'today-button'; + +CalendarHeaderView.prototype.onCurrentMonthChanged = function() { + this.monthPopupButton.setCurrentMonth(this.calendarPicker.currentMonth()); + this._previousMonthButton.setDisabled( + this.disabled || this.calendarPicker.currentMonth() <= this.calendarPicker.minimumMonth); + this._nextMonthButton.setDisabled( + this.disabled || this.calendarPicker.currentMonth() >= this.calendarPicker.maximumMonth); +}; + +CalendarHeaderView.prototype.onNavigationButtonClick = function(sender) { + if (sender === this._previousMonthButton) + this.calendarPicker.setCurrentMonth( + this.calendarPicker.currentMonth().previous(), CalendarPicker.NavigationBehavior.WithAnimation); + else if (sender === this._nextMonthButton) + this.calendarPicker.setCurrentMonth( + this.calendarPicker.currentMonth().next(), CalendarPicker.NavigationBehavior.WithAnimation); + else + this.calendarPicker.selectRangeContainingDay(Day.createFromToday()); +}; + +/** + * @param {!boolean} disabled + */ +CalendarHeaderView.prototype.setDisabled = function(disabled) { + this.disabled = disabled; + this.monthPopupButton.element.disabled = this.disabled; + this._previousMonthButton.setDisabled( + this.disabled || this.calendarPicker.currentMonth() <= this.calendarPicker.minimumMonth); + this._nextMonthButton.setDisabled( + this.disabled || this.calendarPicker.currentMonth() >= this.calendarPicker.maximumMonth); + var monthContainingToday = Month.createFromToday(); + this._todayButton.setDisabled( + this.disabled || monthContainingToday < this.calendarPicker.minimumMonth || + monthContainingToday > this.calendarPicker.maximumMonth); +}; + +/** + * @constructor + * @extends ListCell + */ +function DayCell() { + ListCell.call(this); + this.element.classList.add(DayCell.ClassNameDayCell); + this.element.style.width = DayCell.Width + 'px'; + this.element.style.height = DayCell.Height + 'px'; + this.element.style.lineHeight = (DayCell.Height - DayCell.PaddingSize * 2) + 'px'; + this.element.setAttribute('role', 'gridcell'); + /** + * @type {?Day} + */ + this.day = null; +}; + +DayCell.prototype = Object.create(ListCell.prototype); + +DayCell.Width = 34; +DayCell.Height = hasInaccuratePointingDevice() ? 34 : 20; +DayCell.PaddingSize = 1; +DayCell.ClassNameDayCell = 'day-cell'; +DayCell.ClassNameHighlighted = 'highlighted'; +DayCell.ClassNameDisabled = 'disabled'; +DayCell.ClassNameCurrentMonth = 'current-month'; +DayCell.ClassNameToday = 'today'; + +DayCell._recycleBin = []; + +DayCell.recycleOrCreate = function() { + return DayCell._recycleBin.pop() || new DayCell(); +}; + +/** + * @return {!Array} + * @override + */ +DayCell.prototype._recycleBin = function() { + return DayCell._recycleBin; +}; + +/** + * @override + */ +DayCell.prototype.throwAway = function() { + ListCell.prototype.throwAway.call(this); + this.day = null; +}; + +/** + * @param {!boolean} highlighted + */ +DayCell.prototype.setHighlighted = function(highlighted) { + if (highlighted) { + this.element.classList.add(DayCell.ClassNameHighlighted); + this.element.setAttribute('aria-selected', 'true'); + } else { + this.element.classList.remove(DayCell.ClassNameHighlighted); + this.element.setAttribute('aria-selected', 'false'); + } +}; + +/** + * @param {!boolean} disabled + */ +DayCell.prototype.setDisabled = function(disabled) { + if (disabled) + this.element.classList.add(DayCell.ClassNameDisabled); + else + this.element.classList.remove(DayCell.ClassNameDisabled); +}; + +/** + * @param {!boolean} selected + */ +DayCell.prototype.setIsInCurrentMonth = function(selected) { + if (selected) + this.element.classList.add(DayCell.ClassNameCurrentMonth); + else + this.element.classList.remove(DayCell.ClassNameCurrentMonth); +}; + +/** + * @param {!boolean} selected + */ +DayCell.prototype.setIsToday = function(selected) { + if (selected) + this.element.classList.add(DayCell.ClassNameToday); + else + this.element.classList.remove(DayCell.ClassNameToday); +}; + +/** + * @param {!Day} day + */ +DayCell.prototype.reset = function(day) { + this.day = day; + this.element.textContent = localizeNumber(this.day.date.toString()); + this.element.setAttribute('aria-label', this.day.format()); + this.element.id = this.day.toString(); + this.show(); +}; + +/** + * @constructor + * @extends ListCell + */ +function WeekNumberCell() { + ListCell.call(this); + this.element.classList.add(WeekNumberCell.ClassNameWeekNumberCell); + this.element.style.width = (WeekNumberCell.Width - WeekNumberCell.SeparatorWidth) + 'px'; + this.element.style.height = WeekNumberCell.Height + 'px'; + this.element.style.lineHeight = (WeekNumberCell.Height - WeekNumberCell.PaddingSize * 2) + 'px'; + /** + * @type {?Week} + */ + this.week = null; +}; + +WeekNumberCell.prototype = Object.create(ListCell.prototype); + +WeekNumberCell.Width = 48; +WeekNumberCell.Height = DayCell.Height; +WeekNumberCell.SeparatorWidth = 1; +WeekNumberCell.PaddingSize = 1; +WeekNumberCell.ClassNameWeekNumberCell = 'week-number-cell'; +WeekNumberCell.ClassNameHighlighted = 'highlighted'; +WeekNumberCell.ClassNameDisabled = 'disabled'; + +WeekNumberCell._recycleBin = []; + +/** + * @return {!Array} + * @override + */ +WeekNumberCell.prototype._recycleBin = function() { + return WeekNumberCell._recycleBin; +}; + +/** + * @return {!WeekNumberCell} + */ +WeekNumberCell.recycleOrCreate = function() { + return WeekNumberCell._recycleBin.pop() || new WeekNumberCell(); +}; + +/** + * @param {!Week} week + */ +WeekNumberCell.prototype.reset = function(week) { + this.week = week; + this.element.id = week.toString(); + this.element.setAttribute('role', 'gridcell'); + this.element.setAttribute( + 'aria-label', window.pagePopupController.formatWeek(week.year, week.week, week.firstDay().format())); + this.element.textContent = localizeNumber(this.week.week.toString()); + this.show(); +}; + +/** + * @override + */ +WeekNumberCell.prototype.throwAway = function() { + ListCell.prototype.throwAway.call(this); + this.week = null; +}; + +WeekNumberCell.prototype.setHighlighted = function(highlighted) { + if (highlighted) { + this.element.classList.add(WeekNumberCell.ClassNameHighlighted); + this.element.setAttribute('aria-selected', 'true'); + } else { + this.element.classList.remove(WeekNumberCell.ClassNameHighlighted); + this.element.setAttribute('aria-selected', 'false'); + } +}; + +WeekNumberCell.prototype.setDisabled = function(disabled) { + if (disabled) + this.element.classList.add(WeekNumberCell.ClassNameDisabled); + else + this.element.classList.remove(WeekNumberCell.ClassNameDisabled); +}; + +/** + * @constructor + * @extends View + * @param {!boolean} hasWeekNumberColumn + */ +function CalendarTableHeaderView(hasWeekNumberColumn) { + View.call(this, createElement('div', 'calendar-table-header-view')); + if (hasWeekNumberColumn) { + var weekNumberLabelElement = createElement('div', 'week-number-label', global.params.weekLabel); + weekNumberLabelElement.style.width = WeekNumberCell.Width + 'px'; + this.element.appendChild(weekNumberLabelElement); + } + for (var i = 0; i < DaysPerWeek; ++i) { + var weekDayNumber = (global.params.weekStartDay + i) % DaysPerWeek; + var labelElement = createElement('div', 'week-day-label', global.params.dayLabels[weekDayNumber]); + labelElement.style.width = DayCell.Width + 'px'; + this.element.appendChild(labelElement); + if (getLanguage() === 'ja') { + if (weekDayNumber === 0) + labelElement.style.color = 'red'; + else if (weekDayNumber === 6) + labelElement.style.color = 'blue'; + } + } +} + +CalendarTableHeaderView.prototype = Object.create(View.prototype); + +CalendarTableHeaderView.Height = 25; + +/** + * @constructor + * @extends ListCell + */ +function CalendarRowCell() { + ListCell.call(this); + this.element.classList.add(CalendarRowCell.ClassNameCalendarRowCell); + this.element.style.height = CalendarRowCell.Height + 'px'; + this.element.setAttribute('role', 'row'); + + /** + * @type {!Array} + * @protected + */ + this._dayCells = []; + /** + * @type {!number} + */ + this.row = 0; + /** + * @type {?CalendarTableView} + */ + this.calendarTableView = null; +} + +CalendarRowCell.prototype = Object.create(ListCell.prototype); + +CalendarRowCell.Height = DayCell.Height; +CalendarRowCell.ClassNameCalendarRowCell = 'calendar-row-cell'; + +CalendarRowCell._recycleBin = []; + +/** + * @return {!Array} + * @override + */ +CalendarRowCell.prototype._recycleBin = function() { + return CalendarRowCell._recycleBin; +}; + +/** + * @param {!number} row + * @param {!CalendarTableView} calendarTableView + */ +CalendarRowCell.prototype.reset = function(row, calendarTableView) { + this.row = row; + this.calendarTableView = calendarTableView; + if (this.calendarTableView.hasWeekNumberColumn) { + var middleDay = this.calendarTableView.dayAtColumnAndRow(3, row); + var week = Week.createFromDay(middleDay); + this.weekNumberCell = this.calendarTableView.prepareNewWeekNumberCell(week); + this.weekNumberCell.attachTo(this); + } + var day = calendarTableView.dayAtColumnAndRow(0, row); + for (var i = 0; i < DaysPerWeek; ++i) { + var dayCell = this.calendarTableView.prepareNewDayCell(day); + dayCell.attachTo(this); + this._dayCells.push(dayCell); + day = day.next(); + } + this.show(); +}; + +/** + * @override + */ +CalendarRowCell.prototype.throwAway = function() { + ListCell.prototype.throwAway.call(this); + if (this.weekNumberCell) + this.calendarTableView.throwAwayWeekNumberCell(this.weekNumberCell); + this._dayCells.forEach(this.calendarTableView.throwAwayDayCell, this.calendarTableView); + this._dayCells.length = 0; +}; + +/** + * @constructor + * @extends ListView + * @param {!CalendarPicker} calendarPicker + */ +function CalendarTableView(calendarPicker) { + ListView.call(this); + this.element.classList.add(CalendarTableView.ClassNameCalendarTableView); + this.element.tabIndex = 0; + + /** + * @type {!boolean} + * @const + */ + this.hasWeekNumberColumn = calendarPicker.type === 'week'; + /** + * @type {!CalendarPicker} + * @const + */ + this.calendarPicker = calendarPicker; + /** + * @type {!Object} + * @const + */ + this._dayCells = {}; + var headerView = new CalendarTableHeaderView(this.hasWeekNumberColumn); + headerView.attachTo(this, this.scrollView); + + if (this.hasWeekNumberColumn) { + this.setWidth(DayCell.Width * DaysPerWeek + WeekNumberCell.Width); + /** + * @type {?Array} + * @const + */ + this._weekNumberCells = []; + } else { + this.setWidth(DayCell.Width * DaysPerWeek); + } + + /** + * @type {!boolean} + * @protected + */ + this._ignoreMouseOutUntillNextMouseOver = false; + + this.element.addEventListener('click', this.onClick, false); + this.element.addEventListener('mouseover', this.onMouseOver, false); + this.element.addEventListener('mouseout', this.onMouseOut, false); + + // You shouldn't be able to use the mouse wheel to scroll. + this.scrollView.element.removeEventListener('mousewheel', this.scrollView.onMouseWheel, false); + // You shouldn't be able to do gesture scroll. + this.scrollView.element.removeEventListener('touchstart', this.scrollView.onTouchStart, false); +} + +CalendarTableView.prototype = Object.create(ListView.prototype); + +CalendarTableView.BorderWidth = 1; +CalendarTableView.ClassNameCalendarTableView = 'calendar-table-view'; + +/** + * @param {!number} scrollOffset + * @return {!number} + */ +CalendarTableView.prototype.rowAtScrollOffset = function(scrollOffset) { + return Math.floor(scrollOffset / CalendarRowCell.Height); +}; + +/** + * @param {!number} row + * @return {!number} + */ +CalendarTableView.prototype.scrollOffsetForRow = function(row) { + return row * CalendarRowCell.Height; +}; + +/** + * @param {?Event} event + */ +CalendarTableView.prototype.onClick = function(event) { + if (this.hasWeekNumberColumn) { + var weekNumberCellElement = enclosingNodeOrSelfWithClass(event.target, WeekNumberCell.ClassNameWeekNumberCell); + if (weekNumberCellElement) { + var weekNumberCell = weekNumberCellElement.$view; + this.calendarPicker.selectRangeContainingDay(weekNumberCell.week.firstDay()); + return; + } + } + var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell); + if (!dayCellElement) + return; + var dayCell = dayCellElement.$view; + this.calendarPicker.selectRangeContainingDay(dayCell.day); +}; + +/** + * @param {?Event} event + */ +CalendarTableView.prototype.onMouseOver = function(event) { + if (this.hasWeekNumberColumn) { + var weekNumberCellElement = enclosingNodeOrSelfWithClass(event.target, WeekNumberCell.ClassNameWeekNumberCell); + if (weekNumberCellElement) { + var weekNumberCell = weekNumberCellElement.$view; + this.calendarPicker.highlightRangeContainingDay(weekNumberCell.week.firstDay()); + this._ignoreMouseOutUntillNextMouseOver = false; + return; + } + } + var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell); + if (!dayCellElement) + return; + var dayCell = dayCellElement.$view; + this.calendarPicker.highlightRangeContainingDay(dayCell.day); + this._ignoreMouseOutUntillNextMouseOver = false; +}; + +/** + * @param {?Event} event + */ +CalendarTableView.prototype.onMouseOut = function(event) { + if (this._ignoreMouseOutUntillNextMouseOver) + return; + var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell); + if (!dayCellElement) { + this.calendarPicker.highlightRangeContainingDay(null); + } +}; + +/** + * @param {!number} row + * @return {!CalendarRowCell} + */ +CalendarTableView.prototype.prepareNewCell = function(row) { + var cell = CalendarRowCell._recycleBin.pop() || new CalendarRowCell(); + cell.reset(row, this); + return cell; +}; + +/** + * @return {!number} Height in pixels. + */ +CalendarTableView.prototype.height = function() { + return this.scrollView.height() + CalendarTableHeaderView.Height + CalendarTableView.BorderWidth * 2; +}; + +/** + * @param {!number} height Height in pixels. + */ +CalendarTableView.prototype.setHeight = function(height) { + this.scrollView.setHeight(height - CalendarTableHeaderView.Height - CalendarTableView.BorderWidth * 2); +}; + +/** + * @param {!Month} month + * @param {!boolean} animate + */ +CalendarTableView.prototype.scrollToMonth = function(month, animate) { + var rowForFirstDayInMonth = this.columnAndRowForDay(month.firstDay()).row; + this.scrollView.scrollTo(this.scrollOffsetForRow(rowForFirstDayInMonth), animate); +}; + +/** + * @param {!number} column + * @param {!number} row + * @return {!Day} + */ +CalendarTableView.prototype.dayAtColumnAndRow = function(column, row) { + var daysSinceMinimum = row * DaysPerWeek + column + global.params.weekStartDay - CalendarTableView._MinimumDayWeekDay; + return Day.createFromValue(daysSinceMinimum * MillisecondsPerDay + CalendarTableView._MinimumDayValue); +}; + +CalendarTableView._MinimumDayValue = Day.Minimum.valueOf(); +CalendarTableView._MinimumDayWeekDay = Day.Minimum.weekDay(); + +/** + * @param {!Day} day + * @return {!Object} Object with properties column and row. + */ +CalendarTableView.prototype.columnAndRowForDay = function(day) { + var daysSinceMinimum = (day.valueOf() - CalendarTableView._MinimumDayValue) / MillisecondsPerDay; + var offset = daysSinceMinimum + CalendarTableView._MinimumDayWeekDay - global.params.weekStartDay; + var row = Math.floor(offset / DaysPerWeek); + var column = offset - row * DaysPerWeek; + return {column: column, row: row}; +}; + +CalendarTableView.prototype.updateCells = function() { + ListView.prototype.updateCells.call(this); + + var selection = this.calendarPicker.selection(); + var firstDayInSelection; + var lastDayInSelection; + if (selection) { + firstDayInSelection = selection.firstDay().valueOf(); + lastDayInSelection = selection.lastDay().valueOf(); + } else { + firstDayInSelection = Infinity; + lastDayInSelection = Infinity; + } + var highlight = this.calendarPicker.highlight(); + var firstDayInHighlight; + var lastDayInHighlight; + if (highlight) { + firstDayInHighlight = highlight.firstDay().valueOf(); + lastDayInHighlight = highlight.lastDay().valueOf(); + } else { + firstDayInHighlight = Infinity; + lastDayInHighlight = Infinity; + } + var currentMonth = this.calendarPicker.currentMonth(); + var firstDayInCurrentMonth = currentMonth.firstDay().valueOf(); + var lastDayInCurrentMonth = currentMonth.lastDay().valueOf(); + var activeCell = null; + for (var dayString in this._dayCells) { + var dayCell = this._dayCells[dayString]; + var day = dayCell.day; + dayCell.setIsToday(Day.createFromToday().equals(day)); + dayCell.setSelected(day >= firstDayInSelection && day <= lastDayInSelection); + var isHighlighted = day >= firstDayInHighlight && day <= lastDayInHighlight; + dayCell.setHighlighted(isHighlighted); + if (isHighlighted) { + if (firstDayInHighlight == lastDayInHighlight) + activeCell = dayCell; + else if (this.calendarPicker.type == 'month' && day == firstDayInHighlight) + activeCell = dayCell; + } + dayCell.setIsInCurrentMonth(day >= firstDayInCurrentMonth && day <= lastDayInCurrentMonth); + dayCell.setDisabled(!this.calendarPicker.isValidDay(day)); + } + if (this.hasWeekNumberColumn) { + for (var weekString in this._weekNumberCells) { + var weekNumberCell = this._weekNumberCells[weekString]; + var week = weekNumberCell.week; + var isWeekHighlighted = highlight && highlight.equals(week); + weekNumberCell.setSelected(selection && selection.equals(week)); + weekNumberCell.setHighlighted(isWeekHighlighted); + if (isWeekHighlighted) + activeCell = weekNumberCell; + weekNumberCell.setDisabled(!this.calendarPicker.isValid(week)); + } + } + if (activeCell) { + // Ensure a layoutObject because an element with no layoutObject doesn't post + // activedescendant events. This shouldn't run in the above |for| loop + // to avoid CSS transition. + activeCell.element.offsetLeft; + this.element.setAttribute('aria-activedescendant', activeCell.element.id); + } +}; + +/** + * @param {!Day} day + * @return {!DayCell} + */ +CalendarTableView.prototype.prepareNewDayCell = function(day) { + var dayCell = DayCell.recycleOrCreate(); + dayCell.reset(day); + if (this.calendarPicker.type == 'month') + dayCell.element.setAttribute('aria-label', Month.createFromDay(day).toLocaleString()); + this._dayCells[dayCell.day.toString()] = dayCell; + return dayCell; +}; + +/** + * @param {!Week} week + * @return {!WeekNumberCell} + */ +CalendarTableView.prototype.prepareNewWeekNumberCell = function(week) { + var weekNumberCell = WeekNumberCell.recycleOrCreate(); + weekNumberCell.reset(week); + this._weekNumberCells[weekNumberCell.week.toString()] = weekNumberCell; + return weekNumberCell; +}; + +/** + * @param {!DayCell} dayCell + */ +CalendarTableView.prototype.throwAwayDayCell = function(dayCell) { + delete this._dayCells[dayCell.day.toString()]; + dayCell.throwAway(); +}; + +/** + * @param {!WeekNumberCell} weekNumberCell + */ +CalendarTableView.prototype.throwAwayWeekNumberCell = function(weekNumberCell) { + delete this._weekNumberCells[weekNumberCell.week.toString()]; + weekNumberCell.throwAway(); +}; + +/** + * @constructor + * @extends View + * @param {!Object} config + */ +function CalendarPicker(type, config) { + View.call(this, createElement('div', CalendarPicker.ClassNameCalendarPicker)); + this.element.classList.add(CalendarPicker.ClassNamePreparing); + + /** + * @type {!string} + * @const + */ + this.type = type; + if (this.type === 'week') + this._dateTypeConstructor = Week; + else if (this.type === 'month') + this._dateTypeConstructor = Month; + else + this._dateTypeConstructor = Day; + /** + * @type {!Object} + * @const + */ + this.config = {}; + this._setConfig(config); + /** + * @type {!Month} + * @const + */ + this.minimumMonth = Month.createFromDay(this.config.minimum.firstDay()); + /** + * @type {!Month} + * @const + */ + this.maximumMonth = Month.createFromDay(this.config.maximum.lastDay()); + if (global.params.isLocaleRTL) + this.element.classList.add('rtl'); + /** + * @type {!CalendarTableView} + * @const + */ + this.calendarTableView = new CalendarTableView(this); + this.calendarTableView.hasNumberColumn = this.type === 'week'; + /** + * @type {!CalendarHeaderView} + * @const + */ + this.calendarHeaderView = new CalendarHeaderView(this); + this.calendarHeaderView.monthPopupButton.on(MonthPopupButton.EventTypeButtonClick, this.onMonthPopupButtonClick); + /** + * @type {!MonthPopupView} + * @const + */ + this.monthPopupView = new MonthPopupView(this.minimumMonth, this.maximumMonth); + this.monthPopupView.yearListView.on( + YearListView.EventTypeYearListViewDidSelectMonth, this.onYearListViewDidSelectMonth); + this.monthPopupView.yearListView.on(YearListView.EventTypeYearListViewDidHide, this.onYearListViewDidHide); + this.calendarHeaderView.attachTo(this); + this.calendarTableView.attachTo(this); + /** + * @type {!Month} + * @protected + */ + this._currentMonth = new Month(NaN, NaN); + /** + * @type {?DateType} + * @protected + */ + this._selection = null; + /** + * @type {?DateType} + * @protected + */ + this._highlight = null; + this.calendarTableView.element.addEventListener('keydown', this.onCalendarTableKeyDown, false); + document.body.addEventListener('keydown', this.onBodyKeyDown, false); + + window.addEventListener('resize', this.onWindowResize, false); + + /** + * @type {!number} + * @protected + */ + this._height = -1; + + var initialSelection = parseDateString(config.currentValue); + if (initialSelection) { + this.setCurrentMonth(Month.createFromDay(initialSelection.middleDay()), CalendarPicker.NavigationBehavior.None); + this.setSelection(initialSelection); + } else + this.setCurrentMonth(Month.createFromToday(), CalendarPicker.NavigationBehavior.None); +} + +CalendarPicker.prototype = Object.create(View.prototype); + +CalendarPicker.Padding = 10; +CalendarPicker.BorderWidth = 1; +CalendarPicker.ClassNameCalendarPicker = 'calendar-picker'; +CalendarPicker.ClassNamePreparing = 'preparing'; +CalendarPicker.EventTypeCurrentMonthChanged = 'currentMonthChanged'; +CalendarPicker.commitDelayMs = 100; + +/** + * @param {!Event} event + */ +CalendarPicker.prototype.onWindowResize = function(event) { + this.element.classList.remove(CalendarPicker.ClassNamePreparing); + window.removeEventListener('resize', this.onWindowResize, false); +}; + +/** + * @param {!YearListView} sender + */ +CalendarPicker.prototype.onYearListViewDidHide = function(sender) { + this.monthPopupView.hide(); + this.calendarHeaderView.setDisabled(false); + this.adjustHeight(); +}; + +/** + * @param {!YearListView} sender + * @param {!Month} month + */ +CalendarPicker.prototype.onYearListViewDidSelectMonth = function(sender, month) { + this.setCurrentMonth(month, CalendarPicker.NavigationBehavior.None); +}; + +/** + * @param {!View|Node} parent + * @param {?View|Node=} before + * @override + */ +CalendarPicker.prototype.attachTo = function(parent, before) { + View.prototype.attachTo.call(this, parent, before); + this.calendarTableView.element.focus(); +}; + +CalendarPicker.prototype.cleanup = function() { + window.removeEventListener('resize', this.onWindowResize, false); + this.calendarTableView.element.removeEventListener('keydown', this.onBodyKeyDown, false); + // Month popup view might be attached to document.body. + this.monthPopupView.hide(); +}; + +/** + * @param {?MonthPopupButton} sender + */ +CalendarPicker.prototype.onMonthPopupButtonClick = function(sender) { + var clientRect = this.calendarTableView.element.getBoundingClientRect(); + var calendarTableRect = new Rectangle( + clientRect.left + document.body.scrollLeft, clientRect.top + document.body.scrollTop, clientRect.width, + clientRect.height); + this.monthPopupView.show(this.currentMonth(), calendarTableRect); + this.calendarHeaderView.setDisabled(true); + this.adjustHeight(); +}; + +CalendarPicker.prototype._setConfig = function(config) { + this.config.minimum = (typeof config.min !== 'undefined' && config.min) ? parseDateString(config.min) : + this._dateTypeConstructor.Minimum; + this.config.maximum = (typeof config.max !== 'undefined' && config.max) ? parseDateString(config.max) : + this._dateTypeConstructor.Maximum; + this.config.minimumValue = this.config.minimum.valueOf(); + this.config.maximumValue = this.config.maximum.valueOf(); + this.config.step = (typeof config.step !== undefined) ? Number(config.step) : this._dateTypeConstructor.DefaultStep; + this.config.stepBase = + (typeof config.stepBase !== 'undefined') ? Number(config.stepBase) : this._dateTypeConstructor.DefaultStepBase; +}; + +/** + * @return {!Month} + */ +CalendarPicker.prototype.currentMonth = function() { + return this._currentMonth; +}; + +/** + * @enum {number} + */ +CalendarPicker.NavigationBehavior = { + None: 0, + WithAnimation: 1 +}; + +/** + * @param {!Month} month + * @param {!CalendarPicker.NavigationBehavior} animate + */ +CalendarPicker.prototype.setCurrentMonth = function(month, behavior) { + if (month > this.maximumMonth) + month = this.maximumMonth; + else if (month < this.minimumMonth) + month = this.minimumMonth; + if (this._currentMonth.equals(month)) + return; + this._currentMonth = month; + this.calendarTableView.scrollToMonth( + this._currentMonth, behavior === CalendarPicker.NavigationBehavior.WithAnimation); + this.adjustHeight(); + this.calendarTableView.setNeedsUpdateCells(true); + this.dispatchEvent(CalendarPicker.EventTypeCurrentMonthChanged, {target: this}); +}; + +CalendarPicker.prototype.adjustHeight = function() { + var rowForFirstDayInMonth = this.calendarTableView.columnAndRowForDay(this._currentMonth.firstDay()).row; + var rowForLastDayInMonth = this.calendarTableView.columnAndRowForDay(this._currentMonth.lastDay()).row; + var numberOfRows = rowForLastDayInMonth - rowForFirstDayInMonth + 1; + var calendarTableViewHeight = + CalendarTableHeaderView.Height + numberOfRows * DayCell.Height + CalendarTableView.BorderWidth * 2; + var height = (this.monthPopupView.isVisible ? YearListView.Height : calendarTableViewHeight) + + CalendarHeaderView.Height + CalendarHeaderView.BottomMargin + CalendarPicker.Padding * 2 + + CalendarPicker.BorderWidth * 2; + this.setHeight(height); +}; + +CalendarPicker.prototype.selection = function() { + return this._selection; +}; + +CalendarPicker.prototype.highlight = function() { + return this._highlight; +}; + +/** + * @return {!Day} + */ +CalendarPicker.prototype.firstVisibleDay = function() { + var firstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row; + var firstVisibleDay = this.calendarTableView.dayAtColumnAndRow(0, firstVisibleRow); + if (!firstVisibleDay) + firstVisibleDay = Day.Minimum; + return firstVisibleDay; +}; + +/** + * @return {!Day} + */ +CalendarPicker.prototype.lastVisibleDay = function() { + var lastVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().lastDay()).row; + var lastVisibleDay = this.calendarTableView.dayAtColumnAndRow(DaysPerWeek - 1, lastVisibleRow); + if (!lastVisibleDay) + lastVisibleDay = Day.Maximum; + return lastVisibleDay; +}; + +/** + * @param {?Day} day + */ +CalendarPicker.prototype.selectRangeContainingDay = function(day) { + var selection = day ? this._dateTypeConstructor.createFromDay(day) : null; + this.setSelectionAndCommit(selection); +}; + +/** + * @param {?Day} day + */ +CalendarPicker.prototype.highlightRangeContainingDay = function(day) { + var highlight = day ? this._dateTypeConstructor.createFromDay(day) : null; + this._setHighlight(highlight); +}; + +/** + * Select the specified date. + * @param {?DateType} dayOrWeekOrMonth + */ +CalendarPicker.prototype.setSelection = function(dayOrWeekOrMonth) { + if (!this._selection && !dayOrWeekOrMonth) + return; + if (this._selection && this._selection.equals(dayOrWeekOrMonth)) + return; + var firstDayInSelection = dayOrWeekOrMonth.firstDay(); + var lastDayInSelection = dayOrWeekOrMonth.lastDay(); + var candidateCurrentMonth = Month.createFromDay(firstDayInSelection); + if (this.firstVisibleDay() > lastDayInSelection || this.lastVisibleDay() < firstDayInSelection) { + // Change current month if the selection is not visible at all. + this.setCurrentMonth(candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation); + } else if (this.firstVisibleDay() < firstDayInSelection || this.lastVisibleDay() > lastDayInSelection) { + // If the selection is partly visible, only change the current month if + // doing so will make the whole selection visible. + var firstVisibleRow = this.calendarTableView.columnAndRowForDay(candidateCurrentMonth.firstDay()).row; + var firstVisibleDay = this.calendarTableView.dayAtColumnAndRow(0, firstVisibleRow); + var lastVisibleRow = this.calendarTableView.columnAndRowForDay(candidateCurrentMonth.lastDay()).row; + var lastVisibleDay = this.calendarTableView.dayAtColumnAndRow(DaysPerWeek - 1, lastVisibleRow); + if (firstDayInSelection >= firstVisibleDay && lastDayInSelection <= lastVisibleDay) + this.setCurrentMonth(candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation); + } + this._setHighlight(dayOrWeekOrMonth); + if (!this.isValid(dayOrWeekOrMonth)) + return; + this._selection = dayOrWeekOrMonth; + this.calendarTableView.setNeedsUpdateCells(true); +}; + +/** + * Select the specified date, commit it, and close the popup. + * @param {?DateType} dayOrWeekOrMonth + */ +CalendarPicker.prototype.setSelectionAndCommit = function(dayOrWeekOrMonth) { + this.setSelection(dayOrWeekOrMonth); + // Redraw the widget immidiately, and wait for some time to give feedback to + // a user. + this.element.offsetLeft; + var value = this._selection.toString(); + if (CalendarPicker.commitDelayMs == 0) { + // For testing. + window.pagePopupController.setValueAndClosePopup(0, value); + } else if (CalendarPicker.commitDelayMs < 0) { + // For testing. + window.pagePopupController.setValue(value); + } else { + setTimeout(function() { + window.pagePopupController.setValueAndClosePopup(0, value); + }, CalendarPicker.commitDelayMs); + } +}; + +/** + * @param {?DateType} dayOrWeekOrMonth + */ +CalendarPicker.prototype._setHighlight = function(dayOrWeekOrMonth) { + if (!this._highlight && !dayOrWeekOrMonth) + return; + if (!dayOrWeekOrMonth && !this._highlight) + return; + if (this._highlight && this._highlight.equals(dayOrWeekOrMonth)) + return; + this._highlight = dayOrWeekOrMonth; + this.calendarTableView.setNeedsUpdateCells(true); +}; + +/** + * @param {!number} value + * @return {!boolean} + */ +CalendarPicker.prototype._stepMismatch = function(value) { + var nextAllowedValue = + Math.ceil((value - this.config.stepBase) / this.config.step) * this.config.step + this.config.stepBase; + return nextAllowedValue >= value + this._dateTypeConstructor.DefaultStep; +}; + +/** + * @param {!number} value + * @return {!boolean} + */ +CalendarPicker.prototype._outOfRange = function(value) { + return value < this.config.minimumValue || value > this.config.maximumValue; +}; + +/** + * @param {!DateType} dayOrWeekOrMonth + * @return {!boolean} + */ +CalendarPicker.prototype.isValid = function(dayOrWeekOrMonth) { + var value = dayOrWeekOrMonth.valueOf(); + return dayOrWeekOrMonth instanceof this._dateTypeConstructor && !this._outOfRange(value) && + !this._stepMismatch(value); +}; + +/** + * @param {!Day} day + * @return {!boolean} + */ +CalendarPicker.prototype.isValidDay = function(day) { + return this.isValid(this._dateTypeConstructor.createFromDay(day)); +}; + +/** + * @param {!DateType} dateRange + * @return {!boolean} Returns true if the highlight was changed. + */ +CalendarPicker.prototype._moveHighlight = function(dateRange) { + if (!dateRange) + return false; + if (this._outOfRange(dateRange.valueOf())) + return false; + if (this.firstVisibleDay() > dateRange.middleDay() || this.lastVisibleDay() < dateRange.middleDay()) + this.setCurrentMonth(Month.createFromDay(dateRange.middleDay()), CalendarPicker.NavigationBehavior.WithAnimation); + this._setHighlight(dateRange); + return true; +}; + +/** + * @param {?Event} event + */ +CalendarPicker.prototype.onCalendarTableKeyDown = function(event) { + var key = event.key; + var eventHandled = false; + if (key == 't') { + this.selectRangeContainingDay(Day.createFromToday()); + eventHandled = true; + } else if (key == 'PageUp') { + var previousMonth = this.currentMonth().previous(); + if (previousMonth && previousMonth >= this.config.minimumValue) { + this.setCurrentMonth(previousMonth, CalendarPicker.NavigationBehavior.WithAnimation); + eventHandled = true; + } + } else if (key == 'PageDown') { + var nextMonth = this.currentMonth().next(); + if (nextMonth && nextMonth >= this.config.minimumValue) { + this.setCurrentMonth(nextMonth, CalendarPicker.NavigationBehavior.WithAnimation); + eventHandled = true; + } + } else if (this._highlight) { + if (global.params.isLocaleRTL ? key == 'ArrowRight' : key == 'ArrowLeft') { + eventHandled = this._moveHighlight(this._highlight.previous()); + } else if (key == 'ArrowUp') { + eventHandled = this._moveHighlight(this._highlight.previous(this.type === 'date' ? DaysPerWeek : 1)); + } else if (global.params.isLocaleRTL ? key == 'ArrowLeft' : key == 'ArrowRight') { + eventHandled = this._moveHighlight(this._highlight.next()); + } else if (key == 'ArrowDown') { + eventHandled = this._moveHighlight(this._highlight.next(this.type === 'date' ? DaysPerWeek : 1)); + } else if (key == 'Enter') { + this.setSelectionAndCommit(this._highlight); + } + } else if (key == 'ArrowLeft' || key == 'ArrowUp' || key == 'ArrowRight' || key == 'ArrowDown') { + // Highlight range near the middle. + this.highlightRangeContainingDay(this.currentMonth().middleDay()); + eventHandled = true; + } + + if (eventHandled) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +/** + * @return {!number} Width in pixels. + */ +CalendarPicker.prototype.width = function() { + return this.calendarTableView.width() + + (CalendarTableView.BorderWidth + CalendarPicker.BorderWidth + CalendarPicker.Padding) * 2; +}; + +/** + * @return {!number} Height in pixels. + */ +CalendarPicker.prototype.height = function() { + return this._height; +}; + +/** + * @param {!number} height Height in pixels. + */ +CalendarPicker.prototype.setHeight = function(height) { + if (this._height === height) + return; + this._height = height; + resizeWindow(this.width(), this._height); + this.calendarTableView.setHeight( + this._height - CalendarHeaderView.Height - CalendarHeaderView.BottomMargin - CalendarPicker.Padding * 2 - + CalendarTableView.BorderWidth * 2); +}; + +/** + * @param {?Event} event + */ +CalendarPicker.prototype.onBodyKeyDown = function(event) { + var key = event.key; + var eventHandled = false; + var offset = 0; + switch (key) { + case 'Escape': + window.pagePopupController.closePopup(); + eventHandled = true; + break; + case 'm': + case 'M': + offset = offset || 1; // Fall-through. + case 'y': + case 'Y': + offset = offset || MonthsPerYear; // Fall-through. + case 'd': + case 'D': + offset = offset || MonthsPerYear * 10; + var oldFirstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row; + this.setCurrentMonth( + event.shiftKey ? this.currentMonth().previous(offset) : this.currentMonth().next(offset), + CalendarPicker.NavigationBehavior.WithAnimation); + var newFirstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row; + if (this._highlight) { + var highlightMiddleDay = this._highlight.middleDay(); + this.highlightRangeContainingDay( + highlightMiddleDay.next((newFirstVisibleRow - oldFirstVisibleRow) * DaysPerWeek)); + } + eventHandled = true; + break; + } + if (eventHandled) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +if (window.dialogArguments) { + initialize(dialogArguments); +} else { + window.addEventListener('message', handleMessage, false); +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css new file mode 100644 index 00000000000..eda7bbbc068 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.css @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +body { + -webkit-user-select: none; + background-color: white; + font: -webkit-small-control; + margin: 0; + overflow: hidden; +} + +#main { + background-color: white; + border: solid 1px #8899aa; + box-shadow: inset 2px 2px 2px white, + inset -2px -2px 1px rgba(0,0,0,0.1); + padding: 6px; + float: left; +} + +.color-swatch { + float: left; + width: 20px; + height: 20px; + margin: 1px; + padding: 0; + border: 1px solid #e0e0e0; + border-radius: 0; + box-sizing: content-box; +} + +.color-swatch:focus { + border: 1px solid #000000; + outline: none; +} + +.color-swatch-container { + width: 100%; + max-height: 104px; + overflow: auto; + display: flex; + flex-flow: row wrap; + align-items: center; +} + +.other-color { + width: 100%; + margin: 4px 0 0 0; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js new file mode 100644 index 00000000000..aa2477f1381 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/colorSuggestionPicker.js @@ -0,0 +1,185 @@ +'use strict'; +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +var global = {argumentsReceived: false, params: null}; + +/** + * @param {Event} event + */ +function handleMessage(event) { + initialize(JSON.parse(event.data)); + global.argumentsReceived = true; +} + +/** + * @param {!Object} args + */ +function initialize(args) { + global.params = args; + var main = $('main'); + main.innerHTML = ''; + var errorString = validateArguments(args); + if (errorString) { + main.textContent = 'Internal error: ' + errorString; + resizeWindow(main.offsetWidth, main.offsetHeight); + } else + new ColorPicker(main, args); +} + +// The DefaultColorPalette is used when the list of values are empty. +var DefaultColorPalette = [ + '#000000', '#404040', '#808080', '#c0c0c0', '#ffffff', '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00', + '#00ffff', '#4a86e8', '#0000ff', '#9900ff', '#ff00ff' +]; + +function handleArgumentsTimeout() { + if (global.argumentsReceived) + return; + var args = {values: DefaultColorPalette, otherColorLabel: 'Other...'}; + initialize(args); +} + +/** + * @param {!Object} args + * @return {?string} An error message, or null if the argument has no errors. + */ +function validateArguments(args) { + if (!args.values) + return 'No values.'; + if (!args.otherColorLabel) + return 'No otherColorLabel.'; + return null; +} + +function ColorPicker(element, config) { + Picker.call(this, element, config); + this._config = config; + if (this._config.values.length === 0) + this._config.values = DefaultColorPalette; + this._container = null; + this._layout(); + document.body.addEventListener('keydown', this._handleKeyDown.bind(this)); + this._element.addEventListener('mousemove', this._handleMouseMove.bind(this)); + this._element.addEventListener('mousedown', this._handleMouseDown.bind(this)); +} +ColorPicker.prototype = Object.create(Picker.prototype); + +var SwatchBorderBoxWidth = 24; // keep in sync with CSS +var SwatchBorderBoxHeight = 24; // keep in sync with CSS +var SwatchesPerRow = 5; +var SwatchesMaxRow = 4; + +ColorPicker.prototype._layout = function() { + var container = createElement('div', 'color-swatch-container'); + container.addEventListener('click', this._handleSwatchClick.bind(this), false); + for (var i = 0; i < this._config.values.length; ++i) { + var swatch = createElement('button', 'color-swatch'); + swatch.dataset.index = i; + swatch.dataset.value = this._config.values[i]; + swatch.title = this._config.values[i]; + swatch.style.backgroundColor = this._config.values[i]; + container.appendChild(swatch); + } + var containerWidth = SwatchBorderBoxWidth * SwatchesPerRow; + if (this._config.values.length > SwatchesPerRow * SwatchesMaxRow) + containerWidth += getScrollbarWidth(); + container.style.width = containerWidth + 'px'; + container.style.maxHeight = (SwatchBorderBoxHeight * SwatchesMaxRow) + 'px'; + this._element.appendChild(container); + var otherButton = createElement('button', 'other-color', this._config.otherColorLabel); + otherButton.addEventListener('click', this.chooseOtherColor.bind(this), false); + this._element.appendChild(otherButton); + this._container = container; + this._otherButton = otherButton; + var elementWidth = this._element.offsetWidth; + var elementHeight = this._element.offsetHeight; + resizeWindow(elementWidth, elementHeight); +}; + +ColorPicker.prototype.selectColorAtIndex = function(index) { + index = Math.max(Math.min(this._container.childNodes.length - 1, index), 0); + this._container.childNodes[index].focus(); +}; + +ColorPicker.prototype._handleMouseMove = function(event) { + if (event.target.classList.contains('color-swatch')) + event.target.focus(); +}; + +ColorPicker.prototype._handleMouseDown = function(event) { + // Prevent blur. + if (event.target.classList.contains('color-swatch')) + event.preventDefault(); +}; + +ColorPicker.prototype._handleKeyDown = function(event) { + var key = event.key; + if (key === 'Escape') + this.handleCancel(); + else if (key == 'ArrowLeft' || key == 'ArrowUp' || key == 'ArrowRight' || key == 'ArrowDown') { + var selectedElement = document.activeElement; + var index = 0; + if (selectedElement.classList.contains('other-color')) { + if (key != 'ArrowRight' && key != 'ArrowUp') + return; + index = this._container.childNodes.length - 1; + } else if (selectedElement.classList.contains('color-swatch')) { + index = parseInt(selectedElement.dataset.index, 10); + switch (key) { + case 'ArrowLeft': + index--; + break; + case 'ArrowRight': + index++; + break; + case 'ArrowUp': + index -= SwatchesPerRow; + break; + case 'ArrowDown': + index += SwatchesPerRow; + break; + } + if (index > this._container.childNodes.length - 1) { + this._otherButton.focus(); + return; + } + } + this.selectColorAtIndex(index); + } + event.preventDefault(); +}; + +ColorPicker.prototype._handleSwatchClick = function(event) { + if (event.target.classList.contains('color-swatch')) + this.submitValue(event.target.dataset.value); +}; + +if (window.dialogArguments) { + initialize(dialogArguments); +} else { + window.addEventListener('message', handleMessage, false); + window.setTimeout(handleArgumentsTimeout, 1000); +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg b/chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg new file mode 100644 index 00000000000..d0aef2b0469 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/input_alert.svg @@ -0,0 +1,22 @@ +<!-- No XML declaration intentionally --> +<!-- Copyright 2017 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> +<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 47 47" id="icon"> +<rect x="0" y="0" width="47" height="47" rx="4" ry="4" fill="#ffa300"/> + +<rect x="19" y="10" width="9" height="15" fill="#ffbc4f" /> +<rect x="20" y="10" width="7" height="15" fill="#ffffff" /> +<rect x="20" y="9" width="7" height="1" fill="#ffb53d" /> +<rect x="20" y="25" width="7" height="1" fill="#ffd6af" /> +<rect x="20" y="26" width="7" height="1" fill="#ff7e00" /> + +<defs> +<linearGradient id="g1" x1="0" y1="0" x2="0" y2="1"> +<stop offset="0%" stop-color="#ffb53d" /> +<stop offset="100%" stop-color="#ff7e00" /> +</linearGradient> +</defs> +<circle cx="23.5" cy="33" r="4.5" fill="url(#g1)"/> +<circle cx="23.5" cy="33" r="3.5" fill="#ffffff"/> +</svg> diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css new file mode 100644 index 00000000000..5cce9ac3bb3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.css @@ -0,0 +1,18 @@ +select { + display: block; + overflow-y: auto; +} + +select hr { + border-style: none; + border-bottom: 1px solid black; + margin: 0 0.5em; +} + +option, optgroup { + -webkit-padding-end: 2px; +} + +.wrap option { + white-space: pre-wrap; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js new file mode 100644 index 00000000000..6ce6d125af6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/listPicker.js @@ -0,0 +1,456 @@ +'use strict'; +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var global = {argumentsReceived: false, params: null, picker: null}; + +/** + * @param {Event} event + */ +function handleMessage(event) { + window.removeEventListener('message', handleMessage, false); + initialize(JSON.parse(event.data)); + global.argumentsReceived = true; +} + +/** + * @param {!Object} args + */ +function initialize(args) { + global.params = args; + var main = $('main'); + main.innerHTML = ''; + global.picker = new ListPicker(main, args); +} + +function handleArgumentsTimeout() { + if (global.argumentsReceived) + return; + initialize({}); +} + +/** + * @constructor + * @param {!Element} element + * @param {!Object} config + */ +function ListPicker(element, config) { + Picker.call(this, element, config); + window.pagePopupController.selectFontsFromOwnerDocument(document); + this._selectElement = createElement('select'); + this._selectElement.size = 20; + this._element.appendChild(this._selectElement); + this._delayedChildrenConfig = null; + this._delayedChildrenConfigIndex = 0; + this._layout(); + this._selectElement.addEventListener('mouseup', this._handleMouseUp.bind(this), false); + this._selectElement.addEventListener('touchstart', this._handleTouchStart.bind(this), false); + this._selectElement.addEventListener('keydown', this._handleKeyDown.bind(this), false); + this._selectElement.addEventListener('change', this._handleChange.bind(this), false); + window.addEventListener('message', this._handleWindowMessage.bind(this), false); + window.addEventListener('mousemove', this._handleWindowMouseMove.bind(this), false); + this._handleWindowTouchMoveBound = this._handleWindowTouchMove.bind(this); + this._handleWindowTouchEndBound = this._handleWindowTouchEnd.bind(this); + this._handleTouchSelectModeScrollBound = this._handleTouchSelectModeScroll.bind(this); + this.lastMousePositionX = Infinity; + this.lastMousePositionY = Infinity; + this._selectionSetByMouseHover = false; + + this._trackingTouchId = null; + + this._handleWindowDidHide(); + this._selectElement.focus(); + this._selectElement.value = this._config.selectedIndex; +} +ListPicker.prototype = Object.create(Picker.prototype); + +ListPicker.prototype._handleWindowDidHide = function() { + this._fixWindowSize(); + var selectedOption = this._selectElement.options[this._selectElement.selectedIndex]; + if (selectedOption) + selectedOption.scrollIntoView(false); + window.removeEventListener('didHide', this._handleWindowDidHideBound, false); +}; + +ListPicker.prototype._handleWindowMessage = function(event) { + eval(event.data); + if (window.updateData.type === 'update') { + this._config.baseStyle = window.updateData.baseStyle; + this._config.children = window.updateData.children; + this._update(); + if (this._config.anchorRectInScreen.x !== window.updateData.anchorRectInScreen.x || + this._config.anchorRectInScreen.y !== window.updateData.anchorRectInScreen.y || + this._config.anchorRectInScreen.width !== window.updateData.anchorRectInScreen.width || + this._config.anchorRectInScreen.height !== window.updateData.anchorRectInScreen.height) { + this._config.anchorRectInScreen = window.updateData.anchorRectInScreen; + this._fixWindowSize(); + } + } + delete window.updateData; +}; + +// This should be matched to the border width of the internal listbox +// SELECT. See listPicker.css and html.css. +ListPicker.ListboxSelectBorder = 1; + +ListPicker.prototype._handleWindowMouseMove = function(event) { + var visibleTop = ListPicker.ListboxSelectBorder; + var visibleBottom = this._selectElement.offsetHeight - ListPicker.ListboxSelectBorder; + var optionBounds = event.target.getBoundingClientRect(); + if (optionBounds.height >= 1.0) { + // If the height of the visible part of event.target is less than 1px, + // ignore this event because it may be an error by sub-pixel layout. + if (optionBounds.top < visibleTop) { + if (optionBounds.bottom - visibleTop < 1.0) + return; + } else if (optionBounds.bottom > visibleBottom) { + if (visibleBottom - optionBounds.top < 1.0) + return; + } + } + this.lastMousePositionX = event.clientX; + this.lastMousePositionY = event.clientY; + this._highlightOption(event.target); + this._selectionSetByMouseHover = true; + // Prevent the select element from firing change events for mouse input. + event.preventDefault(); +}; + +ListPicker.prototype._handleMouseUp = function(event) { + if (event.target.tagName !== 'OPTION') + return; + window.pagePopupController.setValueAndClosePopup(0, this._selectElement.value); +}; + +ListPicker.prototype._handleTouchStart = function(event) { + if (this._trackingTouchId !== null) + return; + // Enter touch select mode. In touch select mode the highlight follows the + // finger and on touchend the highlighted item is selected. + var touch = event.touches[0]; + this._trackingTouchId = touch.identifier; + this._highlightOption(touch.target); + this._selectionSetByMouseHover = false; + this._selectElement.addEventListener('scroll', this._handleTouchSelectModeScrollBound, false); + window.addEventListener('touchmove', this._handleWindowTouchMoveBound, false); + window.addEventListener('touchend', this._handleWindowTouchEndBound, false); +}; + +ListPicker.prototype._handleTouchSelectModeScroll = function(event) { + this._exitTouchSelectMode(); +}; + +ListPicker.prototype._exitTouchSelectMode = function(event) { + this._trackingTouchId = null; + this._selectElement.removeEventListener('scroll', this._handleTouchSelectModeScrollBound, false); + window.removeEventListener('touchmove', this._handleWindowTouchMoveBound, false); + window.removeEventListener('touchend', this._handleWindowTouchEndBound, false); +}; + +ListPicker.prototype._handleWindowTouchMove = function(event) { + if (this._trackingTouchId === null) + return; + var touch = this._getTouchForId(event.touches, this._trackingTouchId); + if (!touch) + return; + this._highlightOption(document.elementFromPoint(touch.clientX, touch.clientY)); + this._selectionSetByMouseHover = false; +}; + +ListPicker.prototype._handleWindowTouchEnd = function(event) { + if (this._trackingTouchId === null) + return; + var touch = this._getTouchForId(event.changedTouches, this._trackingTouchId); + if (!touch) + return; + var target = document.elementFromPoint(touch.clientX, touch.clientY); + if (target.tagName === 'OPTION' && !target.disabled) + window.pagePopupController.setValueAndClosePopup(0, this._selectElement.value); + this._exitTouchSelectMode(); +}; + +ListPicker.prototype._getTouchForId = function(touchList, id) { + for (var i = 0; i < touchList.length; i++) { + if (touchList[i].identifier === id) + return touchList[i]; + } + return null; +}; + +ListPicker.prototype._highlightOption = function(target) { + if (target.tagName !== 'OPTION' || target.selected || target.disabled) + return; + var savedScrollTop = this._selectElement.scrollTop; + // TODO(tkent): Updating HTMLOptionElement::selected is not efficient. We + // should optimize it, or use an alternative way. + target.selected = true; + this._selectElement.scrollTop = savedScrollTop; +}; + +ListPicker.prototype._handleChange = function(event) { + window.pagePopupController.setValue(this._selectElement.value); + this._selectionSetByMouseHover = false; +}; + +ListPicker.prototype._handleKeyDown = function(event) { + var key = event.key; + if (key === 'Escape') { + window.pagePopupController.closePopup(); + event.preventDefault(); + } else if (key === 'Tab' || key === 'Enter') { + window.pagePopupController.setValueAndClosePopup(0, this._selectElement.value); + event.preventDefault(); + } else if (event.altKey && (key === 'ArrowDown' || key === 'ArrowUp')) { + // We need to add a delay here because, if we do it immediately the key + // press event will be handled by HTMLSelectElement and this popup will + // be reopened. + setTimeout(function() { + window.pagePopupController.closePopup(); + }, 0); + event.preventDefault(); + } +}; + +ListPicker.prototype._fixWindowSize = function() { + this._selectElement.style.height = ''; + var scale = this._config.scaleFactor; + var maxHeight = this._selectElement.offsetHeight; + var noScrollHeight = (this._calculateScrollHeight() + ListPicker.ListboxSelectBorder * 2); + var scrollbarWidth = getScrollbarWidth(); + var elementOffsetWidth = this._selectElement.offsetWidth; + var desiredWindowHeight = noScrollHeight; + var desiredWindowWidth = elementOffsetWidth; + // If we already have a vertical scrollbar, subtract it out, it will get re-added below. + if (this._selectElement.scrollHeight > this._selectElement.clientHeight) + desiredWindowWidth -= scrollbarWidth; + var expectingScrollbar = false; + if (desiredWindowHeight > maxHeight) { + desiredWindowHeight = maxHeight; + // Setting overflow to auto does not increase width for the scrollbar + // so we need to do it manually. + desiredWindowWidth += scrollbarWidth; + expectingScrollbar = true; + } + // Screen coordinate for anchorRectInScreen and windowRect is DIP. + desiredWindowWidth = Math.max(this._config.anchorRectInScreen.width * scale, desiredWindowWidth); + var windowRect = + adjustWindowRect(desiredWindowWidth / scale, desiredWindowHeight / scale, elementOffsetWidth / scale, 0); + // If the available screen space is smaller than maxHeight, we will get an unexpected scrollbar. + if (!expectingScrollbar && windowRect.height < noScrollHeight / scale) { + desiredWindowWidth = windowRect.width * scale + scrollbarWidth; + windowRect = adjustWindowRect(desiredWindowWidth / scale, windowRect.height, windowRect.width, windowRect.height); + } + this._selectElement.style.width = (windowRect.width * scale) + 'px'; + this._selectElement.style.height = (windowRect.height * scale) + 'px'; + this._element.style.height = (windowRect.height * scale) + 'px'; + setWindowRect(windowRect); +}; + +ListPicker.prototype._calculateScrollHeight = function() { + // Element.scrollHeight returns an integer value but this calculate the + // actual fractional value. + // TODO(tkent): This can be too large? crbug.com/579863 + var top = Infinity; + var bottom = -Infinity; + for (var i = 0; i < this._selectElement.children.length; i++) { + var rect = this._selectElement.children[i].getBoundingClientRect(); + // Skip hidden elements. + if (rect.width === 0 && rect.height === 0) + continue; + top = Math.min(top, rect.top); + bottom = Math.max(bottom, rect.bottom); + } + return Math.max(bottom - top, 0); +}; + +ListPicker.prototype._listItemCount = function() { + return this._selectElement.querySelectorAll('option,optgroup,hr').length; +}; + +ListPicker.prototype._layout = function() { + if (this._config.isRTL) + this._element.classList.add('rtl'); + this._selectElement.style.backgroundColor = this._config.baseStyle.backgroundColor; + this._selectElement.style.color = this._config.baseStyle.color; + this._selectElement.style.textTransform = this._config.baseStyle.textTransform; + this._selectElement.style.fontSize = this._config.baseStyle.fontSize + 'px'; + this._selectElement.style.fontFamily = this._config.baseStyle.fontFamily.map(s => '"' + s + '"').join(','); + this._selectElement.style.fontStyle = this._config.baseStyle.fontStyle; + this._selectElement.style.fontVariant = this._config.baseStyle.fontVariant; + this._updateChildren(this._selectElement, this._config); +}; + +ListPicker.prototype._update = function() { + var scrollPosition = this._selectElement.scrollTop; + var oldValue = this._selectElement.value; + this._layout(); + this._selectElement.value = this._config.selectedIndex; + this._selectElement.scrollTop = scrollPosition; + var optionUnderMouse = null; + if (this._selectionSetByMouseHover) { + var elementUnderMouse = document.elementFromPoint(this.lastMousePositionX, this.lastMousePositionY); + optionUnderMouse = elementUnderMouse && elementUnderMouse.closest('option'); + } + if (optionUnderMouse) + optionUnderMouse.selected = true; + else + this._selectElement.value = oldValue; + this._selectElement.scrollTop = scrollPosition; + this.dispatchEvent('didUpdate'); +}; + +ListPicker.DelayedLayoutThreshold = 1000; + +/** + * @param {!Element} parent Select element or optgroup element. + * @param {!Object} config + */ +ListPicker.prototype._updateChildren = function(parent, config) { + var outOfDateIndex = 0; + var fragment = null; + var inGroup = parent.tagName === 'OPTGROUP'; + var lastListIndex = -1; + var limit = Math.max(this._config.selectedIndex, ListPicker.DelayedLayoutThreshold); + var i; + for (i = 0; i < config.children.length; ++i) { + if (!inGroup && lastListIndex >= limit) + break; + var childConfig = config.children[i]; + var item = this._findReusableItem(parent, childConfig, outOfDateIndex) || this._createItemElement(childConfig); + this._configureItem(item, childConfig, inGroup); + lastListIndex = item.value ? Number(item.value) : -1; + if (outOfDateIndex < parent.children.length) { + parent.insertBefore(item, parent.children[outOfDateIndex]); + } else { + if (!fragment) + fragment = document.createDocumentFragment(); + fragment.appendChild(item); + } + outOfDateIndex++; + } + if (fragment) { + parent.appendChild(fragment); + } else { + var unused = parent.children.length - outOfDateIndex; + for (var j = 0; j < unused; j++) { + parent.removeChild(parent.lastElementChild); + } + } + if (i < config.children.length) { + // We don't bind |config.children| and |i| to _updateChildrenLater + // because config.children can get invalid before _updateChildrenLater + // is called. + this._delayedChildrenConfig = config.children; + this._delayedChildrenConfigIndex = i; + // Needs some amount of delay to kick the first paint. + setTimeout(this._updateChildrenLater.bind(this), 100); + } +}; + +ListPicker.prototype._updateChildrenLater = function(timeStamp) { + if (!this._delayedChildrenConfig) + return; + var fragment = document.createDocumentFragment(); + var startIndex = this._delayedChildrenConfigIndex; + for (; this._delayedChildrenConfigIndex < this._delayedChildrenConfig.length; ++this._delayedChildrenConfigIndex) { + var childConfig = this._delayedChildrenConfig[this._delayedChildrenConfigIndex]; + var item = this._createItemElement(childConfig); + this._configureItem(item, childConfig, false); + fragment.appendChild(item); + } + this._selectElement.appendChild(fragment); + this._selectElement.classList.add('wrap'); + this._delayedChildrenConfig = null; +}; + +ListPicker.prototype._findReusableItem = function(parent, config, startIndex) { + if (startIndex >= parent.children.length) + return null; + var tagName = 'OPTION'; + if (config.type === 'optgroup') + tagName = 'OPTGROUP'; + else if (config.type === 'separator') + tagName = 'HR'; + for (var i = startIndex; i < parent.children.length; i++) { + var child = parent.children[i]; + if (tagName === child.tagName) { + return child; + } + } + return null; +}; + +ListPicker.prototype._createItemElement = function(config) { + var element; + if (!config.type || config.type === 'option') + element = createElement('option'); + else if (config.type === 'optgroup') + element = createElement('optgroup'); + else if (config.type === 'separator') + element = createElement('hr'); + return element; +}; + +ListPicker.prototype._applyItemStyle = function(element, styleConfig) { + if (!styleConfig) + return; + var style = element.style; + style.visibility = styleConfig.visibility ? styleConfig.visibility : ''; + style.display = styleConfig.display ? styleConfig.display : ''; + style.direction = styleConfig.direction ? styleConfig.direction : ''; + style.unicodeBidi = styleConfig.unicodeBidi ? styleConfig.unicodeBidi : ''; + style.color = styleConfig.color ? styleConfig.color : ''; + style.backgroundColor = styleConfig.backgroundColor ? styleConfig.backgroundColor : ''; + style.fontSize = styleConfig.fontSize ? styleConfig.fontSize + 'px' : ''; + style.fontWeight = styleConfig.fontWeight ? styleConfig.fontWeight : ''; + style.fontFamily = styleConfig.fontFamily ? styleConfig.fontFamily.map(s => '"' + s + '"').join(',') : ''; + style.fontStyle = styleConfig.fontStyle ? styleConfig.fontStyle : ''; + style.fontVariant = styleConfig.fontVariant ? styleConfig.fontVariant : ''; + style.textTransform = styleConfig.textTransform ? styleConfig.textTransform : ''; +}; + +ListPicker.prototype._configureItem = function(element, config, inGroup) { + if (!config.type || config.type === 'option') { + element.label = config.label; + element.value = config.value; + if (config.title) + element.title = config.title; + else + element.removeAttribute('title'); + element.disabled = !!config.disabled + if (config.ariaLabel) + element.setAttribute('aria-label', config.ariaLabel); + else element.removeAttribute('aria-label'); + element.style.webkitPaddingStart = this._config.paddingStart + 'px'; + if (inGroup) { + element.style.webkitMarginStart = (-this._config.paddingStart) + 'px'; + // Should be synchronized with padding-end in listPicker.css. + element.style.webkitMarginEnd = '-2px'; + } + } else if (config.type === 'optgroup') { + element.label = config.label; + element.title = config.title; + element.disabled = config.disabled; + element.setAttribute('aria-label', config.ariaLabel); + this._updateChildren(element, config); + element.style.webkitPaddingStart = this._config.paddingStart + 'px'; + } else if (config.type === 'separator') { + element.title = config.title; + element.disabled = config.disabled; + element.setAttribute('aria-label', config.ariaLabel); + if (inGroup) { + element.style.webkitMarginStart = (-this._config.paddingStart) + 'px'; + // Should be synchronized with padding-end in listPicker.css. + element.style.webkitMarginEnd = '-2px'; + } + } + this._applyItemStyle(element, config.style); +}; + +if (window.dialogArguments) { + initialize(dialogArguments); +} else { + window.addEventListener('message', handleMessage, false); + window.setTimeout(handleArgumentsTimeout, 1000); +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css new file mode 100644 index 00000000000..c0c6ff1fbab --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerButton.css @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +input[type='button'], +button { + -webkit-appearance: none; + -webkit-user-select: none; + background-image: linear-gradient(#ededed, #ededed 38%, #dedede); + border: 1px solid rgba(0, 0, 0, 0.25); + border-radius: 2px; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08), + inset 0 1px 2px rgba(255, 255, 255, 0.75); + color: #444; + font: inherit; + text-shadow: 0 1px 0 rgb(240, 240, 240); + min-height: 2em; + min-width: 4em; + -webkit-padding-end: 10px; + -webkit-padding-start: 10px; + margin: 0; +} + +:enabled:hover:-webkit-any(button, input[type='button']) { + background-image: linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0); + border-color: rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12), inset 0 1px 2px rgba(255, 255, 255, 0.95); + color: black; +} + +:enabled:active:-webkit-any(button, input[type='button']) { + background-image: linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7); + box-shadow: none; + text-shadow: none; +} + +:disabled:-webkit-any(button, input[type='button']) { + background-image: linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6); + border-color: rgba(80, 80, 80, 0.2); + box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.75); + color: #aaa; +} + +:enabled:focus:-webkit-any(button, input[type='button']) { + transition: border-color 200ms; + /* We use border color because it follows the border radius (unlike outline). + * This is particularly noticeable on mac. */ + border-color: rgb(77, 144, 254); + outline: none; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css new file mode 100644 index 00000000000..b08f7538fae --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.css @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +body { + -webkit-user-select: none; + background-color: white; + font: -webkit-small-control; + margin: 0; + overflow: hidden; +} + +.rtl { + direction: rtl; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js new file mode 100644 index 00000000000..d4b6856b22f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/pickerCommon.js @@ -0,0 +1,334 @@ +'use strict'; +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/** + * @param {!string} id + */ +function $(id) { + return document.getElementById(id); +} + +/** + * @param {!string} tagName + * @param {string=} opt_class + * @param {string=} opt_text + * @return {!Element} + */ +function createElement(tagName, opt_class, opt_text) { + var element = document.createElement(tagName); + if (opt_class) + element.setAttribute('class', opt_class); + if (opt_text) + element.appendChild(document.createTextNode(opt_text)); + return element; +} + +/** + * @constructor + * @param {!number|Rectangle|Object} xOrRect + * @param {!number} y + * @param {!number} width + * @param {!number} height + */ +function Rectangle(xOrRect, y, width, height) { + if (typeof xOrRect === 'object') { + y = xOrRect.y; + width = xOrRect.width; + height = xOrRect.height; + xOrRect = xOrRect.x; + } + this.x = xOrRect; + this.y = y; + this.width = width; + this.height = height; +} + +Rectangle.prototype = { + get maxX() { + return this.x + this.width; + }, + get maxY() { + return this.y + this.height; + }, + toString: function() { + return 'Rectangle(' + this.x + ',' + this.y + ',' + this.width + ',' + this.height + ')'; + } +}; + +/** + * @param {!Rectangle} rect1 + * @param {!Rectangle} rect2 + * @return {?Rectangle} + */ +Rectangle.intersection = function(rect1, rect2) { + var x = Math.max(rect1.x, rect2.x); + var maxX = Math.min(rect1.maxX, rect2.maxX); + var y = Math.max(rect1.y, rect2.y); + var maxY = Math.min(rect1.maxY, rect2.maxY); + var width = maxX - x; + var height = maxY - y; + if (width < 0 || height < 0) + return null; + return new Rectangle(x, y, width, height); +}; + +/** + * @param {!number} width in CSS pixel + * @param {!number} height in CSS pixel + */ +function resizeWindow(width, height) { + var zoom = global.params.zoomFactor ? global.params.zoomFactor : 1; + setWindowRect(adjustWindowRect(width * zoom, height * zoom, width * zoom, height * zoom)); +} + +/** + * @param {!number} width in DIP + * @param {!number} height in DIP + * @param {?number} minWidth in DIP + * @param {?number} minHeight in DIP + * @return {!Rectangle} Adjusted rectangle in DIP + */ +function adjustWindowRect(width, height, minWidth, minHeight) { + if (typeof minWidth !== 'number') + minWidth = 0; + if (typeof minHeight !== 'number') + minHeight = 0; + + var windowRect = new Rectangle(0, 0, Math.ceil(width), Math.ceil(height)); + + if (!global.params.anchorRectInScreen) + return windowRect; + + var anchorRect = new Rectangle(global.params.anchorRectInScreen); + var availRect = new Rectangle( + window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight); + + _adjustWindowRectVertically(windowRect, availRect, anchorRect, minHeight); + _adjustWindowRectHorizontally(windowRect, availRect, anchorRect, minWidth); + + return windowRect; +} + +/** + * Arguments are DIPs. + */ +function _adjustWindowRectVertically(windowRect, availRect, anchorRect, minHeight) { + var availableSpaceAbove = anchorRect.y - availRect.y; + availableSpaceAbove = Math.max(0, Math.min(availRect.height, availableSpaceAbove)); + + var availableSpaceBelow = availRect.maxY - anchorRect.maxY; + availableSpaceBelow = Math.max(0, Math.min(availRect.height, availableSpaceBelow)); + if (windowRect.height > availableSpaceBelow && availableSpaceBelow < availableSpaceAbove) { + windowRect.height = Math.min(windowRect.height, availableSpaceAbove); + windowRect.height = Math.max(windowRect.height, minHeight); + windowRect.y = anchorRect.y - windowRect.height; + } else { + windowRect.height = Math.min(windowRect.height, availableSpaceBelow); + windowRect.height = Math.max(windowRect.height, minHeight); + windowRect.y = anchorRect.maxY; + } +} + +/** + * Arguments are DIPs. + */ +function _adjustWindowRectHorizontally(windowRect, availRect, anchorRect, minWidth) { + windowRect.width = Math.min(windowRect.width, availRect.width); + windowRect.width = Math.max(windowRect.width, minWidth); + windowRect.x = anchorRect.x; + // If we are getting clipped, we want to switch alignment to the right side + // of the anchor rect as long as doing so will make the popup not clipped. + var rightAlignedX = windowRect.x + anchorRect.width - windowRect.width; + if (rightAlignedX >= availRect.x && (windowRect.maxX > availRect.maxX || global.params.isRTL)) + windowRect.x = rightAlignedX; +} + +/** + * @param {!Rectangle} rect Window position and size in DIP. + */ +function setWindowRect(rect) { + if (window.frameElement) { + window.frameElement.style.width = rect.width + 'px'; + window.frameElement.style.height = rect.height + 'px'; + } else { + window.pagePopupController.setWindowRect(rect.x, rect.y, rect.width, rect.height); + } +} + +function hideWindow() { + setWindowRect(adjustWindowRect(1, 1, 1, 1)); +} + +/** + * @return {!boolean} + */ +function isWindowHidden() { + // window.innerWidth and innerHeight are zoom-adjusted values. If we call + // setWindowRect with width=100 and the zoom-level is 2.0, innerWidth will + // return 50. + return window.innerWidth <= 1 && window.innerHeight <= 1; +} + +window.addEventListener('resize', function() { + if (isWindowHidden()) + window.dispatchEvent(new CustomEvent('didHide')); + else + window.dispatchEvent(new CustomEvent('didOpenPicker')); +}, false); + +/** + * @return {!number} + */ +function getScrollbarWidth() { + if (typeof window.scrollbarWidth === 'undefined') { + var scrollDiv = document.createElement('div'); + scrollDiv.style.opacity = '0'; + scrollDiv.style.overflow = 'scroll'; + scrollDiv.style.width = '50px'; + scrollDiv.style.height = '50px'; + document.body.appendChild(scrollDiv); + window.scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; + scrollDiv.parentNode.removeChild(scrollDiv); + } + return window.scrollbarWidth; +} + +/** + * @param {!string} className + * @return {?Element} + */ +function enclosingNodeOrSelfWithClass(selfNode, className) { + for (var node = selfNode; node && node !== selfNode.ownerDocument; node = node.parentNode) { + if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className)) + return node; + } + return null; +} + +/** + * @constructor + */ +function EventEmitter(){}; + +/** + * @param {!string} type + * @param {!function({...*})} callback + */ +EventEmitter.prototype.on = function(type, callback) { + console.assert(callback instanceof Function); + if (!this._callbacks) + this._callbacks = {}; + if (!this._callbacks[type]) + this._callbacks[type] = []; + this._callbacks[type].push(callback); +}; + +EventEmitter.prototype.hasListener = function(type) { + if (!this._callbacks) + return false; + var callbacksForType = this._callbacks[type]; + if (!callbacksForType) + return false; + return callbacksForType.length > 0; +}; + +/** + * @param {!string} type + * @param {!function(Object)} callback + */ +EventEmitter.prototype.removeListener = function(type, callback) { + if (!this._callbacks) + return; + var callbacksForType = this._callbacks[type]; + if (!callbacksForType) + return; + callbacksForType.splice(callbacksForType.indexOf(callback), 1); + if (callbacksForType.length === 0) + delete this._callbacks[type]; +}; + +/** + * @param {!string} type + * @param {...*} var_args + */ +EventEmitter.prototype.dispatchEvent = function(type) { + if (!this._callbacks) + return; + var callbacksForType = this._callbacks[type]; + if (!callbacksForType) + return; + callbacksForType = callbacksForType.slice(0); + for (var i = 0; i < callbacksForType.length; ++i) { + callbacksForType[i].apply(this, Array.prototype.slice.call(arguments, 1)); + } +}; + +/** + * @constructor + * @extends EventEmitter + * @param {!Element} element + * @param {!Object} config + */ +function Picker(element, config) { + this._element = element; + this._config = config; +} + +Picker.prototype = Object.create(EventEmitter.prototype); + +/** + * @enum {number} + */ +Picker.Actions = { + SetValue: 0, + Cancel: -1, + ChooseOtherColor: -2 +}; + +/** + * @param {!string} value + */ +Picker.prototype.submitValue = function(value) { + window.pagePopupController.setValue(value); + window.pagePopupController.closePopup(); +}; + +Picker.prototype.handleCancel = function() { + window.pagePopupController.closePopup(); +}; + +Picker.prototype.chooseOtherColor = function() { + window.pagePopupController.setValueAndClosePopup(Picker.Actions.ChooseOtherColor, ''); +}; + +Picker.prototype.cleanup = function() {}; + +window.addEventListener('keyup', function(event) { + // JAWS dispatches extra Alt events and unless we handle them they move the + // focus and close the popup. + if (event.key === 'Alt') + event.preventDefault(); +}, true); diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css new file mode 100644 index 00000000000..0281d0df5bf --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.css @@ -0,0 +1,56 @@ +.suggestion-list { + list-style: none; + padding: 0; + margin: 0; + font: -webkit-small-control; + border: 1px solid #7f9db9; + background-color: white; + overflow: hidden; +} + +.suggestion-list-entry { + white-space: nowrap; + height: 1.73em; + line-height: 1.73em; + -webkit-select: none; + cursor: default; +} + +.suggestion-list-entry:focus { + outline: none; +} + +.suggestion-list-entry .content { + padding: 0 4px; +} + +.suggestion-list-entry .label { + text-align: right; + color: #737373; + float: right; + padding: 0 4px 0 20px; +} + +.rtl .suggestion-list-entry .label { + float: left; + padding: 0 20px 0 4px; +} + +.suggestion-list-entry .title { + direction: ltr; + display: inline-block; +} + +.locale-rtl .suggestion-list-entry .title { + direction: rtl; +} + +.measuring-width .suggestion-list-entry .label { + float: none; + margin-right: 0; +} + +.suggestion-list .separator { + border-top: 1px solid #dcdcdc; + height: 0; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js new file mode 100644 index 00000000000..e56b93ea0e7 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/suggestionPicker.js @@ -0,0 +1,331 @@ +'use strict'; +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/** + * @constructor + * @param {!Element} element + * @param {!Object} config + */ +function SuggestionPicker(element, config) { + Picker.call(this, element, config); + this._isFocusByMouse = false; + this._containerElement = null; + this._setColors(); + this._layout(); + this._fixWindowSize(); + this._handleBodyKeyDownBound = this._handleBodyKeyDown.bind(this); + document.body.addEventListener('keydown', this._handleBodyKeyDownBound); + this._element.addEventListener('mouseout', this._handleMouseOut.bind(this), false); +} +SuggestionPicker.prototype = Object.create(Picker.prototype); + +SuggestionPicker.NumberOfVisibleEntries = 20; + +// An entry needs to be at least this many pixels visible for it to be a visible entry. +SuggestionPicker.VisibleEntryThresholdHeight = 4; + +SuggestionPicker.ActionNames = { + OpenCalendarPicker: 'openCalendarPicker' +}; + +SuggestionPicker.ListEntryClass = 'suggestion-list-entry'; + +SuggestionPicker.validateConfig = function(config) { + if (config.showOtherDateEntry && !config.otherDateLabel) + return 'No otherDateLabel.'; + if (config.suggestionHighlightColor && !config.suggestionHighlightColor) + return 'No suggestionHighlightColor.'; + if (config.suggestionHighlightTextColor && !config.suggestionHighlightTextColor) + return 'No suggestionHighlightTextColor.'; + if (config.suggestionValues.length !== config.localizedSuggestionValues.length) + return 'localizedSuggestionValues.length must equal suggestionValues.length.'; + if (config.suggestionValues.length !== config.suggestionLabels.length) + return 'suggestionLabels.length must equal suggestionValues.length.'; + if (typeof config.inputWidth === 'undefined') + return 'No inputWidth.'; + return null; +}; + +SuggestionPicker.prototype._setColors = function() { + var text = '.' + SuggestionPicker.ListEntryClass + ':focus {\ + background-color: ' + + this._config.suggestionHighlightColor + ';\ + color: ' + + this._config.suggestionHighlightTextColor + '; }'; + text += '.' + SuggestionPicker.ListEntryClass + + ':focus .label { color: ' + this._config.suggestionHighlightTextColor + '; }'; + document.head.appendChild(createElement('style', null, text)); +}; + +SuggestionPicker.prototype.cleanup = function() { + document.body.removeEventListener('keydown', this._handleBodyKeyDownBound, false); +}; + +/** + * @param {!string} title + * @param {!string} label + * @param {!string} value + * @return {!Element} + */ +SuggestionPicker.prototype._createSuggestionEntryElement = function(title, label, value) { + var entryElement = createElement('li', SuggestionPicker.ListEntryClass); + entryElement.tabIndex = 0; + entryElement.dataset.value = value; + var content = createElement('span', 'content'); + entryElement.appendChild(content); + var titleElement = createElement('span', 'title', title); + content.appendChild(titleElement); + if (label) { + var labelElement = createElement('span', 'label', label); + content.appendChild(labelElement); + } + entryElement.addEventListener('mouseover', this._handleEntryMouseOver.bind(this), false); + return entryElement; +}; + +/** + * @param {!string} title + * @param {!string} actionName + * @return {!Element} + */ +SuggestionPicker.prototype._createActionEntryElement = function(title, actionName) { + var entryElement = createElement('li', SuggestionPicker.ListEntryClass); + entryElement.tabIndex = 0; + entryElement.dataset.action = actionName; + var content = createElement('span', 'content'); + entryElement.appendChild(content); + var titleElement = createElement('span', 'title', title); + content.appendChild(titleElement); + entryElement.addEventListener('mouseover', this._handleEntryMouseOver.bind(this), false); + return entryElement; +}; + +/** +* @return {!number} +*/ +SuggestionPicker.prototype._measureMaxContentWidth = function() { + // To measure the required width, we first set the class to "measuring-width" which + // left aligns all the content including label. + this._containerElement.classList.add('measuring-width'); + var maxContentWidth = 0; + var contentElements = this._containerElement.getElementsByClassName('content'); + for (var i = 0; i < contentElements.length; ++i) { + maxContentWidth = Math.max(maxContentWidth, contentElements[i].getBoundingClientRect().width); + } + this._containerElement.classList.remove('measuring-width'); + return maxContentWidth; +}; + +SuggestionPicker.prototype._fixWindowSize = function() { + var ListBorder = 2; + var zoom = this._config.zoomFactor; + var desiredWindowWidth = (this._measureMaxContentWidth() + ListBorder) * zoom; + if (typeof this._config.inputWidth === 'number') + desiredWindowWidth = Math.max(this._config.inputWidth, desiredWindowWidth); + var totalHeight = ListBorder; + var maxHeight = 0; + var entryCount = 0; + for (var i = 0; i < this._containerElement.childNodes.length; ++i) { + var node = this._containerElement.childNodes[i]; + if (node.classList.contains(SuggestionPicker.ListEntryClass)) + entryCount++; + totalHeight += node.offsetHeight; + if (maxHeight === 0 && entryCount == SuggestionPicker.NumberOfVisibleEntries) + maxHeight = totalHeight; + } + var desiredWindowHeight = totalHeight * zoom; + if (maxHeight !== 0 && totalHeight > maxHeight * zoom) { + this._containerElement.style.maxHeight = (maxHeight - ListBorder) + 'px'; + desiredWindowWidth += getScrollbarWidth() * zoom; + desiredWindowHeight = maxHeight * zoom; + this._containerElement.style.overflowY = 'scroll'; + } + var windowRect = adjustWindowRect(desiredWindowWidth, desiredWindowHeight, desiredWindowWidth, 0); + this._containerElement.style.height = (windowRect.height / zoom - ListBorder) + 'px'; + setWindowRect(windowRect); +}; + +SuggestionPicker.prototype._layout = function() { + if (this._config.isRTL) + this._element.classList.add('rtl'); + if (this._config.isLocaleRTL) + this._element.classList.add('locale-rtl'); + this._containerElement = createElement('ul', 'suggestion-list'); + this._containerElement.addEventListener('click', this._handleEntryClick.bind(this), false); + for (var i = 0; i < this._config.suggestionValues.length; ++i) { + this._containerElement.appendChild(this._createSuggestionEntryElement( + this._config.localizedSuggestionValues[i], this._config.suggestionLabels[i], this._config.suggestionValues[i])); + } + if (this._config.showOtherDateEntry) { + // Add separator + var separator = createElement('div', 'separator'); + this._containerElement.appendChild(separator); + + // Add "Other..." entry + var otherEntry = + this._createActionEntryElement(this._config.otherDateLabel, SuggestionPicker.ActionNames.OpenCalendarPicker); + this._containerElement.appendChild(otherEntry); + } + this._element.appendChild(this._containerElement); +}; + +/** + * @param {!Element} entry + */ +SuggestionPicker.prototype.selectEntry = function(entry) { + if (typeof entry.dataset.value !== 'undefined') { + this.submitValue(entry.dataset.value); + } else if (entry.dataset.action === SuggestionPicker.ActionNames.OpenCalendarPicker) { + window.addEventListener('didHide', SuggestionPicker._handleWindowDidHide, false); + hideWindow(); + } +}; + +SuggestionPicker._handleWindowDidHide = function() { + openCalendarPicker(); + window.removeEventListener('didHide', SuggestionPicker._handleWindowDidHide); +}; + +/** + * @param {!Event} event + */ +SuggestionPicker.prototype._handleEntryClick = function(event) { + var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass); + if (!entry) + return; + this.selectEntry(entry); + event.preventDefault(); +}; + +/** + * @return {?Element} + */ +SuggestionPicker.prototype._findFirstVisibleEntry = function() { + var scrollTop = this._containerElement.scrollTop; + var childNodes = this._containerElement.childNodes; + for (var i = 0; i < childNodes.length; ++i) { + var node = childNodes[i]; + if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass)) + continue; + if (node.offsetTop + node.offsetHeight - scrollTop > SuggestionPicker.VisibleEntryThresholdHeight) + return node; + } + return null; +}; + +/** + * @return {?Element} + */ +SuggestionPicker.prototype._findLastVisibleEntry = function() { + var scrollBottom = this._containerElement.scrollTop + this._containerElement.offsetHeight; + var childNodes = this._containerElement.childNodes; + for (var i = childNodes.length - 1; i >= 0; --i) { + var node = childNodes[i]; + if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass)) + continue; + if (scrollBottom - node.offsetTop > SuggestionPicker.VisibleEntryThresholdHeight) + return node; + } + return null; +}; + +/** + * @param {!Event} event + */ +SuggestionPicker.prototype._handleBodyKeyDown = function(event) { + var eventHandled = false; + var key = event.key; + if (key === 'Escape') { + this.handleCancel(); + eventHandled = true; + } else if (key == 'ArrowUp') { + if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) { + for (var node = document.activeElement.previousElementSibling; node; node = node.previousElementSibling) { + if (node.classList.contains(SuggestionPicker.ListEntryClass)) { + this._isFocusByMouse = false; + node.focus(); + break; + } + } + } else { + this._element.querySelector('.' + SuggestionPicker.ListEntryClass + ':last-child').focus(); + } + eventHandled = true; + } else if (key == 'ArrowDown') { + if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) { + for (var node = document.activeElement.nextElementSibling; node; node = node.nextElementSibling) { + if (node.classList.contains(SuggestionPicker.ListEntryClass)) { + this._isFocusByMouse = false; + node.focus(); + break; + } + } + } else { + this._element.querySelector('.' + SuggestionPicker.ListEntryClass + ':first-child').focus(); + } + eventHandled = true; + } else if (key === 'Enter') { + this.selectEntry(document.activeElement); + eventHandled = true; + } else if (key === 'PageUp') { + this._containerElement.scrollTop -= this._containerElement.clientHeight; + // Scrolling causes mouseover event to be called and that tries to move the focus too. + // To prevent flickering we won't focus if the current focus was caused by the mouse. + if (!this._isFocusByMouse) + this._findFirstVisibleEntry().focus(); + eventHandled = true; + } else if (key === 'PageDown') { + this._containerElement.scrollTop += this._containerElement.clientHeight; + if (!this._isFocusByMouse) + this._findLastVisibleEntry().focus(); + eventHandled = true; + } + if (eventHandled) + event.preventDefault(); +}; + +/** + * @param {!Event} event + */ +SuggestionPicker.prototype._handleEntryMouseOver = function(event) { + var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass); + if (!entry) + return; + this._isFocusByMouse = true; + entry.focus(); + event.preventDefault(); +}; + +/** + * @param {!Event} event + */ +SuggestionPicker.prototype._handleMouseOut = function(event) { + if (!document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) + return; + this._isFocusByMouse = false; + document.activeElement.blur(); + event.preventDefault(); +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css b/chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css new file mode 100644 index 00000000000..a8e2df9765a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/resources/validation_bubble.css @@ -0,0 +1,148 @@ +/* Copyright 2017 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +:root { + --bubble-background: white; + --bubble-border-color: gray; + --arrow-size: 8px; +} + +#container { + box-sizing: border-box; + display: inline-block; + font-family: system-ui; + max-width: 50%; + opacity: 0; + position: absolute; + will-change: opacity, transform; +} + +#container.shown-initially { + /* If scaleY is smaller than 0.2, an assertion failure occurs in Skia. */ + transform: scale(0.96, 1); +} + +#container.shown-fully { + opacity: 1.0; + transform: scale(1, 1); + transition: opacity 466.67ms cubic-bezier(0.4, 0, 0.2, 1), transform 1166.67ms cubic-bezier(0.2, 0, 0, 1); +} + +#container.hiding { + opacity: 0; + /* See ValidationMessageClientImpl::HideValidationMessage too */ + transition: opacity 133.33ms cubic-bezier(0.4, 0, 0.2, 1); +} + +#container.shown-initially -webkit-any(#icon, #main-message, #sub-message) { + opacity: 0; +} + +#container.shown-fully -webkit-any(#icon, #main-message, #sub-message) { + opacity: 1; + transition: opacity 700ms cubic-bezier(0.3, 0, 0.1, 1); +} + +#container.hiding -webkit-any(#icon, #main-message, #sub-message) { + opacity: 0; + transition: opacity 116.67ms cubic-bezier(0.4, 0, 0.2, 1); +} + +#bubble-body { + background: var(--bubble-background); + border-radius: 4px; + border: 1px solid var(--bubble-border-color); + box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2); + display: grid; + padding: 8px; +} + +#spacer-top { + display: block; + height: var(--arrow-size); +} + +#outer-arrow-top { + border-color: transparent transparent var(--bubble-border-color) transparent; + border-style: solid; + border-width: 0px var(--arrow-size) var(--arrow-size) var(--arrow-size); + display: block; + left: 10px; + margin-top: 0px; + position: absolute; + width: 0px; +} + +#inner-arrow-top { + border-color: transparent transparent var(--bubble-background) transparent; + border-style: solid; + border-width: 0px var(--arrow-size) var(--arrow-size) var(--arrow-size); + display: block; + left: 10px; + margin-top: 1px; + position: absolute; + width: 0px; +} + +.bottom-arrow #outer-arrow-top, .bottom-arrow #inner-arrow-top, .bottom-arrow #spacer-top { + display: none; +} + +#outer-arrow-bottom, #inner-arrow-bottom, #spacer-bottom { + display: none; +} + +.bottom-arrow #spacer-bottom { + display: block; + height: var(--arrow-size); +} + +.bottom-arrow #outer-arrow-bottom { + border-color: var(--bubble-border-color) transparent transparent transparent; + border-style: solid; + border-width: var(--arrow-size) var(--arrow-size) 0px var(--arrow-size); + display: block; + left: 10px; + margin-top: 0px; + position: absolute; + width: 0px; +} + +.bottom-arrow #inner-arrow-bottom { + border-color: var(--bubble-background) transparent transparent transparent; + border-style: solid; + border-width: var(--arrow-size) var(--arrow-size) 0px var(--arrow-size); + display: block; + left: 10px; + margin-top: -1px; + position: absolute; + width: 0px; +} + +#icon { + grid-row: 1 / 3; + grid-column: 1; + -webkit-margin-end: 8px; +} + +#main-message { + font-size: 14px; + grid-row: 1; + grid-column: 2; + margin-top: 3px; + margin-bottom: 4px; +} + +#sub-message { + color: #444; + font-size: 13px; + grid-row: 2; + grid-column: 2; +} + +body { + margin: 0; + overflow: hidden; +} diff --git a/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc new file mode 100644 index 00000000000..a14a67b7dce --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.cc @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/search_input_type.h" + +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/layout/layout_search_field.h" + +namespace blink { + +using namespace HTMLNames; + +inline SearchInputType::SearchInputType(HTMLInputElement& element) + : BaseTextInputType(element), + search_event_timer_( + element.GetDocument().GetTaskRunner(TaskType::kUserInteraction), + this, + &SearchInputType::SearchEventTimerFired) {} + +InputType* SearchInputType::Create(HTMLInputElement& element) { + return new SearchInputType(element); +} + +void SearchInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeSearch); +} + +LayoutObject* SearchInputType::CreateLayoutObject(const ComputedStyle&) const { + return new LayoutSearchField(&GetElement()); +} + +const AtomicString& SearchInputType::FormControlType() const { + return InputTypeNames::search; +} + +bool SearchInputType::NeedsContainer() const { + return true; +} + +void SearchInputType::CreateShadowSubtree() { + TextFieldInputType::CreateShadowSubtree(); + Element* container = ContainerElement(); + Element* view_port = GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::EditingViewPort()); + DCHECK(container); + DCHECK(view_port); + container->InsertBefore( + SearchFieldCancelButtonElement::Create(GetElement().GetDocument()), + view_port->nextSibling()); +} + +void SearchInputType::HandleKeydownEvent(KeyboardEvent* event) { + if (GetElement().IsDisabledOrReadOnly()) { + TextFieldInputType::HandleKeydownEvent(event); + return; + } + + const String& key = event->key(); + if (key == "Escape") { + GetElement().SetValueForUser(""); + GetElement().OnSearch(); + event->SetDefaultHandled(); + return; + } + TextFieldInputType::HandleKeydownEvent(event); +} + +void SearchInputType::StartSearchEventTimer() { + DCHECK(GetElement().GetLayoutObject()); + unsigned length = GetElement().InnerEditorValue().length(); + + if (!length) { + search_event_timer_.Stop(); + GetElement() + .GetDocument() + .GetTaskRunner(TaskType::kUserInteraction) + ->PostTask(FROM_HERE, WTF::Bind(&HTMLInputElement::OnSearch, + WrapPersistent(&GetElement()))); + return; + } + + // After typing the first key, we wait 0.5 seconds. + // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on. + search_event_timer_.StartOneShot(max(0.2, 0.6 - 0.1 * length), FROM_HERE); +} + +void SearchInputType::DispatchSearchEvent() { + search_event_timer_.Stop(); + GetElement().DispatchEvent(Event::CreateBubble(EventTypeNames::search)); +} + +void SearchInputType::SearchEventTimerFired(TimerBase*) { + GetElement().OnSearch(); +} + +bool SearchInputType::SearchEventsShouldBeDispatched() const { + return GetElement().hasAttribute(incrementalAttr); +} + +void SearchInputType::DidSetValueByUserEdit() { + UpdateCancelButtonVisibility(); + + // If the incremental attribute is set, then dispatch the search event + if (SearchEventsShouldBeDispatched()) + StartSearchEventTimer(); + + TextFieldInputType::DidSetValueByUserEdit(); +} + +void SearchInputType::UpdateView() { + BaseTextInputType::UpdateView(); + UpdateCancelButtonVisibility(); +} + +void SearchInputType::UpdateCancelButtonVisibility() { + Element* button = GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::SearchClearButton()); + if (!button) + return; + if (GetElement().value().IsEmpty()) { + button->SetInlineStyleProperty(CSSPropertyOpacity, 0.0, + CSSPrimitiveValue::UnitType::kNumber); + button->SetInlineStyleProperty(CSSPropertyPointerEvents, CSSValueNone); + } else { + button->RemoveInlineStyleProperty(CSSPropertyOpacity); + button->RemoveInlineStyleProperty(CSSPropertyPointerEvents); + } +} + +bool SearchInputType::SupportsInputModeAttribute() const { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h new file mode 100644 index 00000000000..45b6abc3505 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/search_input_type.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SEARCH_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SEARCH_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h" +#include "third_party/blink/renderer/platform/timer.h" + +namespace blink { + +class SearchInputType final : public BaseTextInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + SearchInputType(HTMLInputElement&); + void CountUsage() override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + const AtomicString& FormControlType() const override; + bool NeedsContainer() const override; + void CreateShadowSubtree() override; + void HandleKeydownEvent(KeyboardEvent*) override; + void DidSetValueByUserEdit() override; + bool SupportsInputModeAttribute() const override; + void UpdateView() override; + void DispatchSearchEvent() override; + + void SearchEventTimerFired(TimerBase*); + bool SearchEventsShouldBeDispatched() const; + void StartSearchEventTimer(); + void UpdateCancelButtonVisibility(); + + TaskRunnerTimer<SearchInputType> search_event_timer_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SEARCH_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc new file mode 100644 index 00000000000..3aaeef9dc0e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/slider_thumb_element.h" + +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/events/touch_event.h" +#include "third_party/blink/renderer/core/frame/event_handler_registry.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/step_range.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/layout/layout_slider_container.h" +#include "third_party/blink/renderer/core/layout/layout_slider_thumb.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" + +namespace blink { + +using namespace HTMLNames; + +inline static bool HasVerticalAppearance(HTMLInputElement* input) { + DCHECK(input->GetLayoutObject()); + const ComputedStyle& slider_style = input->GetLayoutObject()->StyleRef(); + + return slider_style.Appearance() == kSliderVerticalPart; +} + +inline SliderThumbElement::SliderThumbElement(Document& document) + : HTMLDivElement(document), in_drag_mode_(false) {} + +SliderThumbElement* SliderThumbElement::Create(Document& document) { + SliderThumbElement* element = new SliderThumbElement(document); + element->setAttribute(idAttr, ShadowElementNames::SliderThumb()); + return element; +} + +void SliderThumbElement::SetPositionFromValue() { + // Since the code to calculate position is in the LayoutSliderThumb layout + // path, we don't actually update the value here. Instead, we poke at the + // layoutObject directly to trigger layout. + if (GetLayoutObject()) + GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( + LayoutInvalidationReason::kSliderValueChanged); +} + +LayoutObject* SliderThumbElement::CreateLayoutObject(const ComputedStyle&) { + return new LayoutSliderThumb(this); +} + +bool SliderThumbElement::IsDisabledFormControl() const { + return HostInput() && HostInput()->IsDisabledFormControl(); +} + +bool SliderThumbElement::MatchesReadOnlyPseudoClass() const { + return HostInput() && HostInput()->MatchesReadOnlyPseudoClass(); +} + +bool SliderThumbElement::MatchesReadWritePseudoClass() const { + return HostInput() && HostInput()->MatchesReadWritePseudoClass(); +} + +const Node* SliderThumbElement::FocusDelegate() const { + return HostInput(); +} + +void SliderThumbElement::DragFrom(const LayoutPoint& point) { + StartDragging(); + SetPositionFromPoint(point); +} + +void SliderThumbElement::SetPositionFromPoint(const LayoutPoint& point) { + HTMLInputElement* input(HostInput()); + Element* track_element = input->UserAgentShadowRoot()->getElementById( + ShadowElementNames::SliderTrack()); + + if (!input->GetLayoutObject() || !GetLayoutBox() || + !track_element->GetLayoutBox()) + return; + + LayoutPoint offset = LayoutPoint(input->GetLayoutObject()->AbsoluteToLocal( + FloatPoint(point), kUseTransforms)); + bool is_vertical = HasVerticalAppearance(input); + bool is_left_to_right_direction = + GetLayoutBox()->Style()->IsLeftToRightDirection(); + LayoutUnit track_size; + LayoutUnit position; + LayoutUnit current_position; + // We need to calculate currentPosition from absolute points becaue the + // layoutObject for this node is usually on a layer and layoutBox()->x() and + // y() are unusable. + // FIXME: This should probably respect transforms. + LayoutPoint absolute_thumb_origin = + GetLayoutBox()->AbsoluteBoundingBoxRectIgnoringTransforms().Location(); + LayoutPoint absolute_slider_content_origin = + LayoutPoint(input->GetLayoutObject()->LocalToAbsolute()); + IntRect track_bounding_box = + track_element->GetLayoutObject() + ->AbsoluteBoundingBoxRectIgnoringTransforms(); + IntRect input_bounding_box = + input->GetLayoutObject()->AbsoluteBoundingBoxRectIgnoringTransforms(); + if (is_vertical) { + track_size = track_element->GetLayoutBox()->ContentHeight() - + GetLayoutBox()->Size().Height(); + position = offset.Y() - GetLayoutBox()->Size().Height() / 2 - + track_bounding_box.Y() + input_bounding_box.Y() - + GetLayoutBox()->MarginBottom(); + current_position = + absolute_thumb_origin.Y() - absolute_slider_content_origin.Y(); + } else { + track_size = track_element->GetLayoutBox()->ContentWidth() - + GetLayoutBox()->Size().Width(); + position = offset.X() - GetLayoutBox()->Size().Width() / 2 - + track_bounding_box.X() + input_bounding_box.X(); + position -= is_left_to_right_direction ? GetLayoutBox()->MarginLeft() + : GetLayoutBox()->MarginRight(); + current_position = + absolute_thumb_origin.X() - absolute_slider_content_origin.X(); + } + position = std::min(position, track_size).ClampNegativeToZero(); + const Decimal ratio = + Decimal::FromDouble(static_cast<double>(position) / track_size); + const Decimal fraction = + is_vertical || !is_left_to_right_direction ? Decimal(1) - ratio : ratio; + StepRange step_range(input->CreateStepRange(kRejectAny)); + Decimal value = + step_range.ClampValue(step_range.ValueFromProportion(fraction)); + + Decimal closest = input->FindClosestTickMarkValue(value); + if (closest.IsFinite()) { + double closest_fraction = + step_range.ProportionFromValue(closest).ToDouble(); + double closest_ratio = is_vertical || !is_left_to_right_direction + ? 1.0 - closest_fraction + : closest_fraction; + LayoutUnit closest_position(track_size * closest_ratio); + const LayoutUnit snapping_threshold(5); + if ((closest_position - position).Abs() <= snapping_threshold) + value = closest; + } + + String value_string = SerializeForNumberType(value); + if (value_string == input->value()) + return; + + // FIXME: This is no longer being set from renderer. Consider updating the + // method name. + input->SetValueFromRenderer(value_string); + if (GetLayoutObject()) + GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( + LayoutInvalidationReason::kSliderValueChanged); +} + +void SliderThumbElement::StartDragging() { + if (LocalFrame* frame = GetDocument().GetFrame()) { + // Note that we get to here only we through mouse event path. The touch + // events are implicitly captured to the starting element and will be + // handled in handleTouchEvent function. + frame->GetEventHandler().SetPointerCapture(PointerEventFactory::kMouseId, + this); + in_drag_mode_ = true; + } +} + +void SliderThumbElement::StopDragging() { + if (!in_drag_mode_) + return; + + if (LocalFrame* frame = GetDocument().GetFrame()) { + frame->GetEventHandler().ReleasePointerCapture( + PointerEventFactory::kMouseId, this); + } + in_drag_mode_ = false; + if (GetLayoutObject()) + GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( + LayoutInvalidationReason::kSliderValueChanged); + if (HostInput()) + HostInput()->DispatchFormControlChangeEvent(); +} + +void SliderThumbElement::DefaultEventHandler(Event* event) { + if (event->IsPointerEvent() && + event->type() == EventTypeNames::lostpointercapture) { + StopDragging(); + return; + } + + if (!event->IsMouseEvent()) { + HTMLDivElement::DefaultEventHandler(event); + return; + } + + // FIXME: Should handle this readonly/disabled check in more general way. + // Missing this kind of check is likely to occur elsewhere if adding it in + // each shadow element. + HTMLInputElement* input = HostInput(); + if (!input || input->IsDisabledFormControl()) { + StopDragging(); + HTMLDivElement::DefaultEventHandler(event); + return; + } + + MouseEvent* mouse_event = ToMouseEvent(event); + bool is_left_button = mouse_event->button() == + static_cast<short>(WebPointerProperties::Button::kLeft); + const AtomicString& event_type = event->type(); + + // We intentionally do not call event->setDefaultHandled() here because + // MediaControlTimelineElement::defaultEventHandler() wants to handle these + // mouse events. + if (event_type == EventTypeNames::mousedown && is_left_button) { + StartDragging(); + return; + } + if (event_type == EventTypeNames::mouseup && is_left_button) { + StopDragging(); + return; + } + if (event_type == EventTypeNames::mousemove) { + if (in_drag_mode_) + SetPositionFromPoint(LayoutPoint(mouse_event->AbsoluteLocation())); + return; + } + + HTMLDivElement::DefaultEventHandler(event); +} + +bool SliderThumbElement::WillRespondToMouseMoveEvents() { + const HTMLInputElement* input = HostInput(); + if (input && !input->IsDisabledFormControl() && in_drag_mode_) + return true; + + return HTMLDivElement::WillRespondToMouseMoveEvents(); +} + +bool SliderThumbElement::WillRespondToMouseClickEvents() { + const HTMLInputElement* input = HostInput(); + if (input && !input->IsDisabledFormControl()) + return true; + + return HTMLDivElement::WillRespondToMouseClickEvents(); +} + +void SliderThumbElement::DetachLayoutTree(const AttachContext& context) { + if (in_drag_mode_) { + if (LocalFrame* frame = GetDocument().GetFrame()) + frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr); + } + HTMLDivElement::DetachLayoutTree(context); +} + +HTMLInputElement* SliderThumbElement::HostInput() const { + // Only HTMLInputElement creates SliderThumbElement instances as its shadow + // nodes. So, ownerShadowHost() must be an HTMLInputElement. + return ToHTMLInputElement(OwnerShadowHost()); +} + +static const AtomicString& SliderThumbShadowPartId() { + DEFINE_STATIC_LOCAL(const AtomicString, slider_thumb, + ("-webkit-slider-thumb")); + return slider_thumb; +} + +static const AtomicString& MediaSliderThumbShadowPartId() { + DEFINE_STATIC_LOCAL(const AtomicString, media_slider_thumb, + ("-webkit-media-slider-thumb")); + return media_slider_thumb; +} + +const AtomicString& SliderThumbElement::ShadowPseudoId() const { + HTMLInputElement* input = HostInput(); + if (!input || !input->GetLayoutObject()) + return SliderThumbShadowPartId(); + + const ComputedStyle& slider_style = input->GetLayoutObject()->StyleRef(); + switch (slider_style.Appearance()) { + case kMediaSliderPart: + case kMediaSliderThumbPart: + case kMediaVolumeSliderPart: + case kMediaVolumeSliderThumbPart: + return MediaSliderThumbShadowPartId(); + default: + return SliderThumbShadowPartId(); + } +} + +// -------------------------------- + +inline SliderContainerElement::SliderContainerElement(Document& document) + : HTMLDivElement(document), + has_touch_event_handler_(false), + touch_started_(false), + sliding_direction_(kNoMove) { + UpdateTouchEventHandlerRegistry(); +} + +DEFINE_NODE_FACTORY(SliderContainerElement) + +HTMLInputElement* SliderContainerElement::HostInput() const { + return ToHTMLInputElement(OwnerShadowHost()); +} + +LayoutObject* SliderContainerElement::CreateLayoutObject(const ComputedStyle&) { + return new LayoutSliderContainer(this); +} + +void SliderContainerElement::DefaultEventHandler(Event* event) { + if (event->IsTouchEvent()) { + HandleTouchEvent(ToTouchEvent(event)); + return; + } +} + +void SliderContainerElement::HandleTouchEvent(TouchEvent* event) { + HTMLInputElement* input = HostInput(); + if (!input || input->IsDisabledFormControl() || !event) + return; + + if (event->type() == EventTypeNames::touchend) { + // TODO: Also do this for touchcancel? + input->DispatchFormControlChangeEvent(); + event->SetDefaultHandled(); + sliding_direction_ = kNoMove; + touch_started_ = false; + return; + } + + // The direction of this series of touch actions has been determined, which is + // perpendicular to the slider, so no need to adjust the value. + if (!CanSlide()) { + return; + } + + TouchList* touches = event->targetTouches(); + SliderThumbElement* thumb = ToSliderThumbElement( + GetTreeScope().getElementById(ShadowElementNames::SliderThumb())); + if (!thumb || !touches) + return; + + if (touches->length() == 1) { + if (event->type() == EventTypeNames::touchstart) { + start_point_ = touches->item(0)->AbsoluteLocation(); + sliding_direction_ = kNoMove; + touch_started_ = true; + thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation()); + } else if (touch_started_) { + LayoutPoint current_point = touches->item(0)->AbsoluteLocation(); + if (sliding_direction_ == + kNoMove) { // Still needs to update the direction. + sliding_direction_ = GetDirection(current_point, start_point_); + } + + // m_slidingDirection has been updated, so check whether it's okay to + // slide again. + if (CanSlide()) { + thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation()); + event->SetDefaultHandled(); + } + } + } +} + +SliderContainerElement::Direction SliderContainerElement::GetDirection( + LayoutPoint& point1, + LayoutPoint& point2) { + if (point1 == point2) { + return kNoMove; + } + if ((point1.X() - point2.X()).Abs() >= (point1.Y() - point2.Y()).Abs()) { + return kHorizontal; + } + return kVertical; +} + +bool SliderContainerElement::CanSlide() { + if (!HostInput() || !HostInput()->GetLayoutObject() || + !HostInput()->GetLayoutObject()->Style()) { + return false; + } + const ComputedStyle* slider_style = HostInput()->GetLayoutObject()->Style(); + const TransformOperations& transforms = slider_style->Transform(); + int transform_size = transforms.size(); + if (transform_size > 0) { + for (int i = 0; i < transform_size; ++i) { + if (transforms.at(i)->GetType() == TransformOperation::kRotate) { + return true; + } + } + } + if ((sliding_direction_ == kVertical && + slider_style->Appearance() == kSliderHorizontalPart) || + (sliding_direction_ == kHorizontal && + slider_style->Appearance() == kSliderVerticalPart)) { + return false; + } + return true; +} + +const AtomicString& SliderContainerElement::ShadowPseudoId() const { + DEFINE_STATIC_LOCAL(const AtomicString, media_slider_container, + ("-webkit-media-slider-container")); + DEFINE_STATIC_LOCAL(const AtomicString, slider_container, + ("-webkit-slider-container")); + + if (!OwnerShadowHost() || !OwnerShadowHost()->GetLayoutObject()) + return slider_container; + + const ComputedStyle& slider_style = + OwnerShadowHost()->GetLayoutObject()->StyleRef(); + switch (slider_style.Appearance()) { + case kMediaSliderPart: + case kMediaSliderThumbPart: + case kMediaVolumeSliderPart: + case kMediaVolumeSliderThumbPart: + return media_slider_container; + default: + return slider_container; + } +} + +void SliderContainerElement::UpdateTouchEventHandlerRegistry() { + if (has_touch_event_handler_) { + return; + } + if (GetDocument().GetPage() && + GetDocument().Lifecycle().GetState() < DocumentLifecycle::kStopping) { + EventHandlerRegistry& registry = + GetDocument().GetPage()->GetEventHandlerRegistry(); + registry.DidAddEventHandler( + *this, EventHandlerRegistry::kTouchStartOrMoveEventPassive); + registry.DidAddEventHandler(*this, EventHandlerRegistry::kPointerEvent); + has_touch_event_handler_ = true; + } +} + +void SliderContainerElement::DidMoveToNewDocument(Document& old_document) { + UpdateTouchEventHandlerRegistry(); + HTMLElement::DidMoveToNewDocument(old_document); +} + +void SliderContainerElement::RemoveAllEventListeners() { + Node::RemoveAllEventListeners(); + has_touch_event_handler_ = false; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h new file mode 100644 index 00000000000..e4e692391d4 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/slider_thumb_element.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SLIDER_THUMB_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SLIDER_THUMB_ELEMENT_H_ + +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class HTMLInputElement; +class Event; +class TouchEvent; + +class SliderThumbElement final : public HTMLDivElement { + public: + static SliderThumbElement* Create(Document&); + + void SetPositionFromValue(); + + void DragFrom(const LayoutPoint&); + void DefaultEventHandler(Event*) override; + bool WillRespondToMouseMoveEvents() override; + bool WillRespondToMouseClickEvents() override; + void DetachLayoutTree(const AttachContext& = AttachContext()) override; + const AtomicString& ShadowPseudoId() const override; + HTMLInputElement* HostInput() const; + void SetPositionFromPoint(const LayoutPoint&); + void StopDragging(); + + private: + SliderThumbElement(Document&); + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + Element* CloneWithoutAttributesAndChildren(Document&) const override; + bool IsDisabledFormControl() const override; + bool MatchesReadOnlyPseudoClass() const override; + bool MatchesReadWritePseudoClass() const override; + const Node* FocusDelegate() const override; + void StartDragging(); + + bool + in_drag_mode_; // Mouse only. Touch is handled by SliderContainerElement. +}; + +inline Element* SliderThumbElement::CloneWithoutAttributesAndChildren( + Document& factory) const { + return Create(factory); +} + +// FIXME: There are no ways to check if a node is a SliderThumbElement. +DEFINE_ELEMENT_TYPE_CASTS(SliderThumbElement, IsHTMLElement()); + +class SliderContainerElement final : public HTMLDivElement { + public: + enum Direction { + kHorizontal, + kVertical, + kNoMove, + }; + + DECLARE_NODE_FACTORY(SliderContainerElement); + HTMLInputElement* HostInput() const; + void DefaultEventHandler(Event*) override; + void HandleTouchEvent(TouchEvent*); + void UpdateTouchEventHandlerRegistry(); + void DidMoveToNewDocument(Document&) override; + void RemoveAllEventListeners() override; + + private: + explicit SliderContainerElement(Document&); + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + const AtomicString& ShadowPseudoId() const override; + Direction GetDirection(LayoutPoint&, LayoutPoint&); + bool CanSlide(); + + bool has_touch_event_handler_; + bool touch_started_; + Direction sliding_direction_; + LayoutPoint start_point_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc new file mode 100644 index 00000000000..698c5812176 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.cc @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/spin_button_element.h" + +#include "build/build_config.h" +#include "third_party/blink/public/platform/task_type.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/events/wheel_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h" + +namespace blink { + +using namespace HTMLNames; + +inline SpinButtonElement::SpinButtonElement(Document& document, + SpinButtonOwner& spin_button_owner) + : HTMLDivElement(document), + spin_button_owner_(&spin_button_owner), + capturing_(false), + up_down_state_(kIndeterminate), + press_starting_state_(kIndeterminate), + repeating_timer_(document.GetTaskRunner(TaskType::kInternalAnimation), + this, + &SpinButtonElement::RepeatingTimerFired) {} + +SpinButtonElement* SpinButtonElement::Create( + Document& document, + SpinButtonOwner& spin_button_owner) { + SpinButtonElement* element = + new SpinButtonElement(document, spin_button_owner); + element->SetShadowPseudoId(AtomicString("-webkit-inner-spin-button")); + element->setAttribute(idAttr, ShadowElementNames::SpinButton()); + return element; +} + +void SpinButtonElement::DetachLayoutTree(const AttachContext& context) { + ReleaseCapture(kEventDispatchDisallowed); + HTMLDivElement::DetachLayoutTree(context); +} + +void SpinButtonElement::DefaultEventHandler(Event* event) { + if (!event->IsMouseEvent()) { + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); + return; + } + + LayoutBox* box = GetLayoutBox(); + if (!box) { + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); + return; + } + + if (!ShouldRespondToMouseEvents()) { + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); + return; + } + + MouseEvent* mouse_event = ToMouseEvent(event); + IntPoint local = RoundedIntPoint(box->AbsoluteToLocal( + FloatPoint(mouse_event->AbsoluteLocation()), kUseTransforms)); + if (mouse_event->type() == EventTypeNames::mousedown && + mouse_event->button() == + static_cast<short>(WebPointerProperties::Button::kLeft)) { + if (box->PixelSnappedBorderBoxRect().Contains(local)) { + if (spin_button_owner_) + spin_button_owner_->FocusAndSelectSpinButtonOwner(); + if (GetLayoutObject()) { + if (up_down_state_ != kIndeterminate) { + // A JavaScript event handler called in doStepAction() below + // might change the element state and we might need to + // cancel the repeating timer by the state change. If we + // started the timer after doStepAction(), we would have no + // chance to cancel the timer. + StartRepeatingTimer(); + DoStepAction(up_down_state_ == kUp ? 1 : -1); + } + } + event->SetDefaultHandled(); + } + } else if (mouse_event->type() == EventTypeNames::mouseup && + mouse_event->button() == + static_cast<short>(WebPointerProperties::Button::kLeft)) { + ReleaseCapture(); + } else if (event->type() == EventTypeNames::mousemove) { + if (box->PixelSnappedBorderBoxRect().Contains(local)) { + if (!capturing_) { + if (LocalFrame* frame = GetDocument().GetFrame()) { + frame->GetEventHandler().SetCapturingMouseEventsNode(this); + capturing_ = true; + if (Page* page = GetDocument().GetPage()) + page->GetChromeClient().RegisterPopupOpeningObserver(this); + } + } + UpDownState old_up_down_state = up_down_state_; + up_down_state_ = (local.Y() < box->Size().Height() / 2) ? kUp : kDown; + if (up_down_state_ != old_up_down_state) + GetLayoutObject()->SetShouldDoFullPaintInvalidation(); + } else { + ReleaseCapture(); + up_down_state_ = kIndeterminate; + } + } + + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); +} + +void SpinButtonElement::WillOpenPopup() { + ReleaseCapture(); + up_down_state_ = kIndeterminate; +} + +void SpinButtonElement::ForwardEvent(Event* event) { + if (!GetLayoutBox()) + return; + + if (!event->HasInterface(EventNames::WheelEvent)) + return; + + if (!spin_button_owner_) + return; + + if (!spin_button_owner_->ShouldSpinButtonRespondToWheelEvents()) + return; + + DoStepAction(ToWheelEvent(event)->wheelDeltaY()); + event->SetDefaultHandled(); +} + +bool SpinButtonElement::WillRespondToMouseMoveEvents() { + if (GetLayoutBox() && ShouldRespondToMouseEvents()) + return true; + + return HTMLDivElement::WillRespondToMouseMoveEvents(); +} + +bool SpinButtonElement::WillRespondToMouseClickEvents() { + if (GetLayoutBox() && ShouldRespondToMouseEvents()) + return true; + + return HTMLDivElement::WillRespondToMouseClickEvents(); +} + +void SpinButtonElement::DoStepAction(int amount) { + if (!spin_button_owner_) + return; + + if (amount > 0) + spin_button_owner_->SpinButtonStepUp(); + else if (amount < 0) + spin_button_owner_->SpinButtonStepDown(); +} + +void SpinButtonElement::ReleaseCapture(EventDispatch event_dispatch) { + StopRepeatingTimer(); + if (!capturing_) + return; + if (LocalFrame* frame = GetDocument().GetFrame()) { + frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr); + capturing_ = false; + if (Page* page = GetDocument().GetPage()) + page->GetChromeClient().UnregisterPopupOpeningObserver(this); + } + if (spin_button_owner_) + spin_button_owner_->SpinButtonDidReleaseMouseCapture(event_dispatch); +} + +bool SpinButtonElement::MatchesReadOnlyPseudoClass() const { + return OwnerShadowHost()->MatchesReadOnlyPseudoClass(); +} + +bool SpinButtonElement::MatchesReadWritePseudoClass() const { + return OwnerShadowHost()->MatchesReadWritePseudoClass(); +} + +void SpinButtonElement::StartRepeatingTimer() { + press_starting_state_ = up_down_state_; + Page* page = GetDocument().GetPage(); + DCHECK(page); + ScrollbarTheme& theme = page->GetScrollbarTheme(); + repeating_timer_.Start(theme.InitialAutoscrollTimerDelay(), + theme.AutoscrollTimerDelay(), FROM_HERE); +} + +void SpinButtonElement::StopRepeatingTimer() { + repeating_timer_.Stop(); +} + +void SpinButtonElement::Step(int amount) { + if (!ShouldRespondToMouseEvents()) + return; +// On Mac OS, NSStepper updates the value for the button under the mouse +// cursor regardless of the button pressed at the beginning. So the +// following check is not needed for Mac OS. +#if !defined(OS_MACOSX) + if (up_down_state_ != press_starting_state_) + return; +#endif + DoStepAction(amount); +} + +void SpinButtonElement::RepeatingTimerFired(TimerBase*) { + if (up_down_state_ != kIndeterminate) + Step(up_down_state_ == kUp ? 1 : -1); +} + +void SpinButtonElement::SetHovered(bool flag) { + if (!flag) + up_down_state_ = kIndeterminate; + HTMLDivElement::SetHovered(flag); +} + +bool SpinButtonElement::ShouldRespondToMouseEvents() { + return !spin_button_owner_ || + spin_button_owner_->ShouldSpinButtonRespondToMouseEvents(); +} + +void SpinButtonElement::Trace(blink::Visitor* visitor) { + visitor->Trace(spin_button_owner_); + HTMLDivElement::Trace(visitor); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h new file mode 100644 index 00000000000..88dc9b64e12 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/spin_button_element.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SPIN_BUTTON_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SPIN_BUTTON_ELEMENT_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/page/popup_opening_observer.h" +#include "third_party/blink/renderer/platform/timer.h" + +namespace blink { + +class CORE_EXPORT SpinButtonElement final : public HTMLDivElement, + public PopupOpeningObserver { + public: + enum UpDownState { + kIndeterminate, // Hovered, but the event is not handled. + kDown, + kUp, + }; + enum EventDispatch { + kEventDispatchAllowed, + kEventDispatchDisallowed, + }; + class SpinButtonOwner : public GarbageCollectedMixin { + public: + virtual ~SpinButtonOwner() = default; + virtual void FocusAndSelectSpinButtonOwner() = 0; + virtual bool ShouldSpinButtonRespondToMouseEvents() = 0; + virtual bool ShouldSpinButtonRespondToWheelEvents() = 0; + virtual void SpinButtonDidReleaseMouseCapture(EventDispatch) = 0; + virtual void SpinButtonStepDown() = 0; + virtual void SpinButtonStepUp() = 0; + }; + + // The owner of SpinButtonElement must call removeSpinButtonOwner + // because SpinButtonElement can be outlive SpinButtonOwner + // implementation, e.g. during event handling. + static SpinButtonElement* Create(Document&, SpinButtonOwner&); + UpDownState GetUpDownState() const { return up_down_state_; } + void ReleaseCapture(EventDispatch = kEventDispatchAllowed); + void RemoveSpinButtonOwner() { spin_button_owner_ = nullptr; } + + void Step(int amount); + + bool WillRespondToMouseMoveEvents() override; + bool WillRespondToMouseClickEvents() override; + + void ForwardEvent(Event*); + + void Trace(blink::Visitor*) override; + + private: + SpinButtonElement(Document&, SpinButtonOwner&); + + void DetachLayoutTree(const AttachContext&) override; + bool IsSpinButtonElement() const override { return true; } + bool IsDisabledFormControl() const override { + return OwnerShadowHost() && OwnerShadowHost()->IsDisabledFormControl(); + } + bool MatchesReadOnlyPseudoClass() const override; + bool MatchesReadWritePseudoClass() const override; + void DefaultEventHandler(Event*) override; + void WillOpenPopup() override; + void DoStepAction(int); + void StartRepeatingTimer(); + void StopRepeatingTimer(); + void RepeatingTimerFired(TimerBase*); + void SetHovered(bool = true) override; + bool ShouldRespondToMouseEvents(); + bool IsMouseFocusable() const override { return false; } + + Member<SpinButtonOwner> spin_button_owner_; + bool capturing_; + UpDownState up_down_state_; + UpDownState press_starting_state_; + TaskRunnerTimer<SpinButtonElement> repeating_timer_; +}; + +DEFINE_TYPE_CASTS(SpinButtonElement, + Node, + node, + ToElement(node)->IsSpinButtonElement(), + ToElement(node).IsSpinButtonElement()); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/step_range.cc b/chromium/third_party/blink/renderer/core/html/forms/step_range.cc new file mode 100644 index 00000000000..63a3a36d0f9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/step_range.cc @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. 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 "third_party/blink/renderer/core/html/forms/step_range.h" + +#include <float.h> +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using namespace HTMLNames; + +StepRange::StepRange() + : maximum_(100), + minimum_(0), + step_(1), + step_base_(0), + has_step_(false), + has_range_limitations_(false) {} + +StepRange::StepRange(const StepRange& step_range) = default; + +StepRange::StepRange(const Decimal& step_base, + const Decimal& minimum, + const Decimal& maximum, + bool has_range_limitations, + const Decimal& step, + const StepDescription& step_description) + : maximum_(maximum), + minimum_(minimum), + step_(step.IsFinite() ? step : 1), + step_base_(step_base.IsFinite() ? step_base : 1), + step_description_(step_description), + has_step_(step.IsFinite()), + has_range_limitations_(has_range_limitations) { + DCHECK(maximum_.IsFinite()); + DCHECK(minimum_.IsFinite()); + DCHECK(step_.IsFinite()); + DCHECK(step_base_.IsFinite()); +} + +Decimal StepRange::AcceptableError() const { + // FIXME: We should use DBL_MANT_DIG instead of FLT_MANT_DIG regarding to + // HTML5 specification. + DEFINE_STATIC_LOCAL(const Decimal, two_power_of_float_mantissa_bits, + (Decimal::kPositive, 0, UINT64_C(1) << FLT_MANT_DIG)); + return step_description_.step_value_should_be == kStepValueShouldBeReal + ? step_ / two_power_of_float_mantissa_bits + : Decimal(0); +} + +Decimal StepRange::AlignValueForStep(const Decimal& current_value, + const Decimal& new_value) const { + DEFINE_STATIC_LOCAL(const Decimal, ten_power_of21, + (Decimal::kPositive, 21, 1)); + if (new_value >= ten_power_of21) + return new_value; + + return StepMismatch(current_value) ? new_value + : RoundByStep(new_value, step_base_); +} + +Decimal StepRange::ClampValue(const Decimal& value) const { + const Decimal in_range_value = std::max(minimum_, std::min(value, maximum_)); + if (!has_step_) + return in_range_value; + // Rounds inRangeValue to stepBase + N * step. + const Decimal rounded_value = RoundByStep(in_range_value, step_base_); + const Decimal clamped_value = + rounded_value > maximum_ + ? rounded_value - step_ + : (rounded_value < minimum_ ? rounded_value + step_ : rounded_value); + // clampedValue can be outside of [m_minimum, m_maximum] if m_step is huge. + if (clamped_value < minimum_ || clamped_value > maximum_) + return in_range_value; + return clamped_value; +} + +Decimal StepRange::ParseStep(AnyStepHandling any_step_handling, + const StepDescription& step_description, + const String& step_string) { + if (step_string.IsEmpty()) + return step_description.DefaultValue(); + + if (DeprecatedEqualIgnoringCase(step_string, "any")) { + switch (any_step_handling) { + case kRejectAny: + return Decimal::Nan(); + case kAnyIsDefaultStep: + return step_description.DefaultValue(); + default: + NOTREACHED(); + } + } + + Decimal step = ParseToDecimalForNumberType(step_string); + if (!step.IsFinite() || step <= 0) + return step_description.DefaultValue(); + + switch (step_description.step_value_should_be) { + case kStepValueShouldBeReal: + step *= step_description.step_scale_factor; + break; + case kParsedStepValueShouldBeInteger: + // For date, month, and week, the parsed value should be an integer for + // some types. + step = std::max(step.Round(), Decimal(1)); + step *= step_description.step_scale_factor; + break; + case kScaledStepValueShouldBeInteger: + // For datetime, datetime-local, time, the result should be an integer. + step *= step_description.step_scale_factor; + step = std::max(step.Round(), Decimal(1)); + break; + default: + NOTREACHED(); + } + + DCHECK_GT(step, 0); + return step; +} + +Decimal StepRange::RoundByStep(const Decimal& value, + const Decimal& base) const { + return base + ((value - base) / step_).Round() * step_; +} + +bool StepRange::StepMismatch(const Decimal& value_for_check) const { + if (!has_step_) + return false; + if (!value_for_check.IsFinite()) + return false; + const Decimal value = (value_for_check - step_base_).Abs(); + if (!value.IsFinite()) + return false; + // Decimal's fractional part size is DBL_MAN_DIG-bit. If the current value + // is greater than step*2^DBL_MANT_DIG, the following computation for + // remainder makes no sense. + DEFINE_STATIC_LOCAL(const Decimal, two_power_of_double_mantissa_bits, + (Decimal::kPositive, 0, UINT64_C(1) << DBL_MANT_DIG)); + if (value / two_power_of_double_mantissa_bits > step_) + return false; + // The computation follows HTML5 4.10.7.2.10 `The step attribute' : + // ... that number subtracted from the step base is not an integral multiple + // of the allowed value step, the element is suffering from a step mismatch. + const Decimal remainder = (value - step_ * (value / step_).Round()).Abs(); + // Accepts errors in lower fractional part which IEEE 754 single-precision + // can't represent. + const Decimal computed_acceptable_error = AcceptableError(); + return computed_acceptable_error < remainder && + remainder < (step_ - computed_acceptable_error); +} + +Decimal StepRange::StepSnappedMaximum() const { + Decimal base = StepBase(); + Decimal step = Step(); + if (base - step == base || !(base / step).IsFinite()) + return Decimal::Nan(); + Decimal aligned_maximum = base + ((Maximum() - base) / step).Floor() * step; + if (aligned_maximum > Maximum()) + aligned_maximum -= step; + DCHECK_LE(aligned_maximum, Maximum()); + if (aligned_maximum < Minimum()) + return Decimal::Nan(); + return aligned_maximum; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/step_range.h b/chromium/third_party/blink/renderer/core/html/forms/step_range.h new file mode 100644 index 00000000000..b3177715881 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/step_range.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_STEP_RANGE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_STEP_RANGE_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/decimal.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +enum AnyStepHandling { kRejectAny, kAnyIsDefaultStep }; + +class CORE_EXPORT StepRange { + DISALLOW_NEW(); + + public: + enum StepValueShouldBe { + kStepValueShouldBeReal, + kParsedStepValueShouldBeInteger, + kScaledStepValueShouldBeInteger, + }; + + struct StepDescription { + USING_FAST_MALLOC(StepDescription); + + public: + int default_step; + int default_step_base; + int step_scale_factor; + StepValueShouldBe step_value_should_be; + + StepDescription( + int default_step, + int default_step_base, + int step_scale_factor, + StepValueShouldBe step_value_should_be = kStepValueShouldBeReal) + : default_step(default_step), + default_step_base(default_step_base), + step_scale_factor(step_scale_factor), + step_value_should_be(step_value_should_be) {} + + StepDescription() + : default_step(1), + default_step_base(0), + step_scale_factor(1), + step_value_should_be(kStepValueShouldBeReal) {} + + Decimal DefaultValue() const { return default_step * step_scale_factor; } + }; + + StepRange(); + StepRange(const StepRange&); + StepRange(const Decimal& step_base, + const Decimal& minimum, + const Decimal& maximum, + bool has_range_limitations, + const Decimal& step, + const StepDescription&); + + Decimal AlignValueForStep(const Decimal& current_value, + const Decimal& new_value) const; + Decimal ClampValue(const Decimal& value) const; + bool HasStep() const { return has_step_; } + Decimal Maximum() const { return maximum_; } + Decimal Minimum() const { return minimum_; } + // https://html.spec.whatwg.org/multipage/forms.html#have-range-limitations + bool HasRangeLimitations() const { return has_range_limitations_; } + static Decimal ParseStep(AnyStepHandling, + const StepDescription&, + const String&); + Decimal Step() const { return step_; } + Decimal StepBase() const { return step_base_; } + bool StepMismatch(const Decimal&) const; + // Returns the maximum step-matched value between minimum() and + // maximum(). If there's no such value, this returns Decimal::nan(). + Decimal StepSnappedMaximum() const; + + // Clamp the middle value according to the step + Decimal DefaultValue() const { return ClampValue((minimum_ + maximum_) / 2); } + + // Map value into 0-1 range + Decimal ProportionFromValue(const Decimal& value) const { + if (minimum_ == maximum_) + return 0; + + return (value - minimum_) / (maximum_ - minimum_); + } + + // Map from 0-1 range to value + Decimal ValueFromProportion(const Decimal& proportion) const { + return minimum_ + proportion * (maximum_ - minimum_); + } + + private: + StepRange& operator=(const StepRange&) = delete; + Decimal AcceptableError() const; + Decimal RoundByStep(const Decimal& value, const Decimal& base) const; + + const Decimal maximum_; // maximum must be >= minimum. + const Decimal minimum_; + const Decimal step_; + const Decimal step_base_; + const StepDescription step_description_; + const bool has_step_; + const bool has_range_limitations_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_STEP_RANGE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc b/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc new file mode 100644 index 00000000000..eddc58b9b28 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/step_range_test.cc @@ -0,0 +1,36 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/step_range.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +TEST(StepRangeTest, ClampValueWithOutStepMatchedValue) { + // <input type=range value=200 min=0 max=100 step=1000> + StepRange step_range(Decimal(200), Decimal(0), Decimal(100), true, + Decimal(1000), StepRange::StepDescription()); + + EXPECT_EQ(Decimal(100), step_range.ClampValue(Decimal(200))); + EXPECT_EQ(Decimal(0), step_range.ClampValue(Decimal(-100))); +} + +TEST(StepRangeTest, StepSnappedMaximum) { + // <input type=number value="1110" max=100 step="20"> + StepRange step_range(Decimal::FromDouble(1110), Decimal(0), Decimal(100), + true, Decimal(20), StepRange::StepDescription()); + EXPECT_EQ(Decimal(90), step_range.StepSnappedMaximum()); + + // crbug.com/617809 + // <input type=number + // value="8624024784918570374158793713225864658725102756338798521486349461900449498315865014065406918592181034633618363349807887404915072776534917803019477033072906290735591367789665757384135591225430117374220731087966" + // min=0 max=100 step="18446744073709551575"> + StepRange step_range2(Decimal::FromDouble(8.62402e+207), Decimal(0), + Decimal(100), true, Decimal::FromDouble(1.84467e+19), + StepRange::StepDescription()); + EXPECT_FALSE(step_range2.StepSnappedMaximum().IsFinite()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc new file mode 100644 index 00000000000..d4d0a48280a --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.cc @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/submit_input_type.h" + +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +InputType* SubmitInputType::Create(HTMLInputElement& element) { + UseCounter::Count(element.GetDocument(), WebFeature::kInputTypeSubmit); + return new SubmitInputType(element); +} + +const AtomicString& SubmitInputType::FormControlType() const { + return InputTypeNames::submit; +} + +void SubmitInputType::AppendToFormData(FormData& form_data) const { + if (GetElement().IsActivatedSubmit()) + form_data.append(GetElement().GetName(), + GetElement().ValueOrDefaultLabel()); +} + +bool SubmitInputType::SupportsRequired() const { + return false; +} + +void SubmitInputType::HandleDOMActivateEvent(Event* event) { + if (GetElement().IsDisabledFormControl() || !GetElement().Form()) + return; + GetElement().Form()->PrepareForSubmission( + event, &GetElement()); // Event handlers can run. + event->SetDefaultHandled(); +} + +bool SubmitInputType::CanBeSuccessfulSubmitButton() { + return true; +} + +String SubmitInputType::DefaultLabel() const { + return GetLocale().QueryString(WebLocalizedString::kSubmitButtonDefaultLabel); +} + +bool SubmitInputType::IsTextButton() const { + return true; +} + +void SubmitInputType::ValueAttributeChanged() { + UseCounter::Count(GetElement().GetDocument(), + WebFeature::kInputTypeSubmitWithValue); + BaseButtonInputType::ValueAttributeChanged(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h new file mode 100644 index 00000000000..22d94696d35 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/submit_input_type.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_button_input_type.h" + +namespace blink { + +class SubmitInputType final : public BaseButtonInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + SubmitInputType(HTMLInputElement& element) : BaseButtonInputType(element) {} + const AtomicString& FormControlType() const override; + void AppendToFormData(FormData&) const override; + bool SupportsRequired() const override; + void HandleDOMActivateEvent(Event*) override; + bool CanBeSuccessfulSubmitButton() override; + String DefaultLabel() const override; + bool IsTextButton() const override; + void ValueAttributeChanged() override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_SUBMIT_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc new file mode 100644 index 00000000000..75bf3b1677e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/telephone_input_type.h" + +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/input_type_names.h" + +namespace blink { + +InputType* TelephoneInputType::Create(HTMLInputElement& element) { + return new TelephoneInputType(element); +} + +void TelephoneInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeTel); +} + +const AtomicString& TelephoneInputType::FormControlType() const { + return InputTypeNames::tel; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h new file mode 100644 index 00000000000..b9b8ca7131d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/telephone_input_type.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TELEPHONE_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TELEPHONE_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h" + +namespace blink { + +class TelephoneInputType final : public BaseTextInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + TelephoneInputType(HTMLInputElement& element) : BaseTextInputType(element) {} + void CountUsage() override; + const AtomicString& FormControlType() const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TELEPHONE_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc new file mode 100644 index 00000000000..5a4aab14f69 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.cc @@ -0,0 +1,1006 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * 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 "third_party/blink/renderer/core/html/forms/text_control_element.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_messages.h" +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/ax_object_cache.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/element_traversal.h" +#include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/editing/editing_utilities.h" +#include "third_party/blink/renderer/core/editing/editor.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/editing/iterators/character_iterator.h" +#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h" +#include "third_party/blink/renderer/core/editing/position.h" +#include "third_party/blink/renderer/core/editing/selection_template.h" +#include "third_party/blink/renderer/core/editing/serializers/serialization.h" +#include "third_party/blink/renderer/core/editing/set_selection_options.h" +#include "third_party/blink/renderer/core/editing/visible_position.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/use_counter.h" +#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h" +#include "third_party/blink/renderer/core/html/html_br_element.h" +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_block.h" +#include "third_party/blink/renderer/core/layout/layout_block_flow.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/focus_controller.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +using namespace HTMLNames; + +TextControlElement::TextControlElement(const QualifiedName& tag_name, + Document& doc) + : HTMLFormControlElementWithState(tag_name, doc), + last_change_was_user_edit_(false), + cached_selection_start_(0), + cached_selection_end_(0) { + cached_selection_direction_ = + doc.GetFrame() && doc.GetFrame() + ->GetEditor() + .Behavior() + .ShouldConsiderSelectionAsDirectional() + ? kSelectionHasForwardDirection + : kSelectionHasNoDirection; +} + +TextControlElement::~TextControlElement() = default; + +void TextControlElement::DispatchFocusEvent( + Element* old_focused_element, + WebFocusType type, + InputDeviceCapabilities* source_capabilities) { + if (SupportsPlaceholder()) + UpdatePlaceholderVisibility(); + HandleFocusEvent(old_focused_element, type); + HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type, + source_capabilities); +} + +void TextControlElement::DispatchBlurEvent( + Element* new_focused_element, + WebFocusType type, + InputDeviceCapabilities* source_capabilities) { + if (SupportsPlaceholder()) + UpdatePlaceholderVisibility(); + HandleBlurEvent(); + HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type, + source_capabilities); +} + +void TextControlElement::DefaultEventHandler(Event* event) { + if (event->type() == EventTypeNames::webkitEditableContentChanged && + GetLayoutObject() && GetLayoutObject()->IsTextControl()) { + last_change_was_user_edit_ = !GetDocument().IsRunningExecCommand(); + + if (IsFocused()) { + // Updating the cache in SelectionChanged() isn't enough because + // SelectionChanged() is not called if: + // - Text nodes in the inner-editor is split to multiple, and + // - The caret is on the beginning of a Text node, and its previous node + // is updated, or + // - The caret is on the end of a text node, and its next node is updated. + CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(), + ComputeSelectionDirection()); + } + + SubtreeHasChanged(); + return; + } + + HTMLFormControlElementWithState::DefaultEventHandler(event); +} + +void TextControlElement::ForwardEvent(Event* event) { + if (event->type() == EventTypeNames::blur || + event->type() == EventTypeNames::focus) + return; + InnerEditorElement()->DefaultEventHandler(event); +} + +String TextControlElement::StrippedPlaceholder() const { + // According to the HTML5 specification, we need to remove CR and LF from + // the attribute value. + const AtomicString& attribute_value = FastGetAttribute(placeholderAttr); + if (!attribute_value.Contains(kNewlineCharacter) && + !attribute_value.Contains(kCarriageReturnCharacter)) + return attribute_value; + + StringBuilder stripped; + unsigned length = attribute_value.length(); + stripped.ReserveCapacity(length); + for (unsigned i = 0; i < length; ++i) { + UChar character = attribute_value[i]; + if (character == kNewlineCharacter || character == kCarriageReturnCharacter) + continue; + stripped.Append(character); + } + return stripped.ToString(); +} + +static bool IsNotLineBreak(UChar ch) { + return ch != kNewlineCharacter && ch != kCarriageReturnCharacter; +} + +bool TextControlElement::IsPlaceholderEmpty() const { + const AtomicString& attribute_value = FastGetAttribute(placeholderAttr); + return attribute_value.GetString().Find(IsNotLineBreak) == kNotFound; +} + +bool TextControlElement::PlaceholderShouldBeVisible() const { + return SupportsPlaceholder() && InnerEditorValue().IsEmpty() && + !IsPlaceholderEmpty() && SuggestedValue().IsEmpty(); +} + +HTMLElement* TextControlElement::PlaceholderElement() const { + if (!SupportsPlaceholder()) + return nullptr; + DCHECK(UserAgentShadowRoot()); + return ToHTMLElementOrDie( + UserAgentShadowRoot()->getElementById(ShadowElementNames::Placeholder())); +} + +void TextControlElement::UpdatePlaceholderVisibility() { + HTMLElement* placeholder = PlaceholderElement(); + if (!placeholder) { + UpdatePlaceholderText(); + return; + } + + bool place_holder_was_visible = IsPlaceholderVisible(); + SetPlaceholderVisibility(PlaceholderShouldBeVisible()); + + placeholder->SetInlineStyleProperty( + CSSPropertyDisplay, + IsPlaceholderVisible() || !SuggestedValue().IsEmpty() ? CSSValueBlock + : CSSValueNone, + true); + + // If there was a visibility change not caused by the suggested value, set + // that the pseudo state changed. + if (place_holder_was_visible != IsPlaceholderVisible() && + SuggestedValue().IsEmpty()) { + PseudoStateChanged(CSSSelector::kPseudoPlaceholderShown); + } +} + +void TextControlElement::setSelectionStart(unsigned start) { + setSelectionRangeForBinding(start, std::max(start, selectionEnd()), + selectionDirection()); +} + +void TextControlElement::setSelectionEnd(unsigned end) { + setSelectionRangeForBinding(std::min(end, selectionStart()), end, + selectionDirection()); +} + +void TextControlElement::setSelectionDirection(const String& direction) { + setSelectionRangeForBinding(selectionStart(), selectionEnd(), direction); +} + +void TextControlElement::select() { + setSelectionRangeForBinding(0, std::numeric_limits<unsigned>::max()); + // Avoid SelectionBehaviorOnFocus::Restore, which scrolls containers to show + // the selection. + focus( + FocusParams(SelectionBehaviorOnFocus::kNone, kWebFocusTypeNone, nullptr)); + RestoreCachedSelection(); +} + +void TextControlElement::SetValueBeforeFirstUserEditIfNotSet() { + if (!value_before_first_user_edit_.IsNull()) + return; + String value = this->value(); + value_before_first_user_edit_ = value.IsNull() ? g_empty_string : value; +} + +void TextControlElement::CheckIfValueWasReverted(const String& value) { + DCHECK(!value_before_first_user_edit_.IsNull()) + << "setValueBeforeFirstUserEditIfNotSet should be called beforehand."; + String non_null_value = value.IsNull() ? g_empty_string : value; + if (value_before_first_user_edit_ == non_null_value) + ClearValueBeforeFirstUserEdit(); +} + +void TextControlElement::ClearValueBeforeFirstUserEdit() { + value_before_first_user_edit_ = String(); +} + +void TextControlElement::SetFocused(bool flag, WebFocusType focus_type) { + HTMLFormControlElementWithState::SetFocused(flag, focus_type); + + if (!flag) + DispatchFormControlChangeEvent(); +} + +void TextControlElement::DispatchFormControlChangeEvent() { + if (!value_before_first_user_edit_.IsNull() && + !EqualIgnoringNullity(value_before_first_user_edit_, value())) { + ClearValueBeforeFirstUserEdit(); + DispatchChangeEvent(); + } else { + ClearValueBeforeFirstUserEdit(); + } +} + +void TextControlElement::EnqueueChangeEvent() { + if (!value_before_first_user_edit_.IsNull() && + !EqualIgnoringNullity(value_before_first_user_edit_, value())) { + Event* event = Event::CreateBubble(EventTypeNames::change); + event->SetTarget(this); + GetDocument().EnqueueAnimationFrameEvent(event); + } + ClearValueBeforeFirstUserEdit(); +} + +void TextControlElement::setRangeText(const String& replacement, + ExceptionState& exception_state) { + setRangeText(replacement, selectionStart(), selectionEnd(), "preserve", + exception_state); +} + +void TextControlElement::setRangeText(const String& replacement, + unsigned start, + unsigned end, + const String& selection_mode, + ExceptionState& exception_state) { + if (start > end) { + exception_state.ThrowDOMException( + kIndexSizeError, "The provided start value (" + String::Number(start) + + ") is larger than the provided end value (" + + String::Number(end) + ")."); + return; + } + if (OpenShadowRoot()) + return; + + String text = InnerEditorValue(); + unsigned text_length = text.length(); + unsigned replacement_length = replacement.length(); + unsigned new_selection_start = selectionStart(); + unsigned new_selection_end = selectionEnd(); + + start = std::min(start, text_length); + end = std::min(end, text_length); + + if (start < end) + text.replace(start, end - start, replacement); + else + text.insert(replacement, start); + + setValue(text, TextFieldEventBehavior::kDispatchNoEvent, + TextControlSetValueSelection::kDoNotSet); + + if (selection_mode == "select") { + new_selection_start = start; + new_selection_end = start + replacement_length; + } else if (selection_mode == "start") { + new_selection_start = new_selection_end = start; + } else if (selection_mode == "end") { + new_selection_start = new_selection_end = start + replacement_length; + } else { + DCHECK_EQ(selection_mode, "preserve"); + long delta = replacement_length - (end - start); + + if (new_selection_start > end) + new_selection_start += delta; + else if (new_selection_start > start) + new_selection_start = start; + + if (new_selection_end > end) + new_selection_end += delta; + else if (new_selection_end > start) + new_selection_end = start + replacement_length; + } + + setSelectionRangeForBinding(new_selection_start, new_selection_end); +} + +void TextControlElement::setSelectionRangeForBinding( + unsigned start, + unsigned end, + const String& direction_string) { + TextFieldSelectionDirection direction = kSelectionHasNoDirection; + if (direction_string == "forward") + direction = kSelectionHasForwardDirection; + else if (direction_string == "backward") + direction = kSelectionHasBackwardDirection; + if (SetSelectionRange(start, end, direction)) + ScheduleSelectEvent(); +} + +static Position PositionForIndex(HTMLElement* inner_editor, unsigned index) { + if (index == 0) { + Node* node = NodeTraversal::Next(*inner_editor, inner_editor); + if (node && node->IsTextNode()) + return Position(node, 0); + return Position(inner_editor, 0); + } + unsigned remaining_characters_to_move_forward = index; + Node* last_br_or_text = inner_editor; + for (Node& node : NodeTraversal::DescendantsOf(*inner_editor)) { + if (node.HasTagName(brTag)) { + if (remaining_characters_to_move_forward == 0) + return Position::BeforeNode(node); + --remaining_characters_to_move_forward; + last_br_or_text = &node; + continue; + } + + if (node.IsTextNode()) { + Text& text = ToText(node); + if (remaining_characters_to_move_forward < text.length()) + return Position(&text, remaining_characters_to_move_forward); + remaining_characters_to_move_forward -= text.length(); + last_br_or_text = &node; + continue; + } + + NOTREACHED(); + } + DCHECK(last_br_or_text); + return LastPositionInOrAfterNode(*last_br_or_text); +} + +unsigned TextControlElement::IndexForPosition(HTMLElement* inner_editor, + const Position& passed_position) { + if (!inner_editor || !inner_editor->contains(passed_position.AnchorNode()) || + passed_position.IsNull()) + return 0; + + if (Position::BeforeNode(*inner_editor) == passed_position) + return 0; + + unsigned index = 0; + Node* start_node = passed_position.ComputeNodeBeforePosition(); + if (!start_node) + start_node = passed_position.ComputeContainerNode(); + if (start_node == inner_editor && passed_position.IsAfterAnchor()) + start_node = inner_editor->lastChild(); + DCHECK(start_node); + DCHECK(inner_editor->contains(start_node)); + + for (Node* node = start_node; node; + node = NodeTraversal::Previous(*node, inner_editor)) { + if (node->IsTextNode()) { + int length = ToText(*node).length(); + if (node == passed_position.ComputeContainerNode()) + index += std::min(length, passed_position.OffsetInContainerNode()); + else + index += length; + } else if (node->HasTagName(brTag)) { + ++index; + } + } + + return index; +} + +bool TextControlElement::SetSelectionRange( + unsigned start, + unsigned end, + TextFieldSelectionDirection direction) { + if (OpenShadowRoot() || !IsTextControl()) + return false; + const unsigned editor_value_length = InnerEditorValue().length(); + end = std::min(end, editor_value_length); + start = std::min(start, end); + LocalFrame* frame = GetDocument().GetFrame(); + if (direction == kSelectionHasNoDirection && frame && + frame->GetEditor().Behavior().ShouldConsiderSelectionAsDirectional()) + direction = kSelectionHasForwardDirection; + bool did_change = CacheSelection(start, end, direction); + + if (GetDocument().FocusedElement() != this) + return did_change; + + HTMLElement* inner_editor = InnerEditorElement(); + if (!frame || !inner_editor) + return did_change; + + Position start_position = PositionForIndex(inner_editor, start); + Position end_position = + start == end ? start_position : PositionForIndex(inner_editor, end); + + DCHECK_EQ(start, IndexForPosition(inner_editor, start_position)); + DCHECK_EQ(end, IndexForPosition(inner_editor, end_position)); + +#if DCHECK_IS_ON() + // startPosition and endPosition can be null position for example when + // "-webkit-user-select: none" style attribute is specified. + if (start_position.IsNotNull() && end_position.IsNotNull()) { + DCHECK_EQ(start_position.AnchorNode()->OwnerShadowHost(), this); + DCHECK_EQ(end_position.AnchorNode()->OwnerShadowHost(), this); + } +#endif // DCHECK_IS_ON() + frame->Selection().SetSelection( + SelectionInDOMTree::Builder() + .Collapse(direction == kSelectionHasBackwardDirection + ? end_position + : start_position) + .Extend(direction == kSelectionHasBackwardDirection ? start_position + : end_position) + .Build(), + SetSelectionOptions::Builder() + .SetShouldCloseTyping(true) + .SetShouldClearTypingStyle(true) + .SetDoNotSetFocus(true) + .SetIsDirectional(direction != kSelectionHasNoDirection) + .Build()); + return did_change; +} + +bool TextControlElement::CacheSelection(unsigned start, + unsigned end, + TextFieldSelectionDirection direction) { + DCHECK_LE(start, end); + bool did_change = cached_selection_start_ != start || + cached_selection_end_ != end || + cached_selection_direction_ != direction; + cached_selection_start_ = start; + cached_selection_end_ = end; + cached_selection_direction_ = direction; + return did_change; +} + +VisiblePosition TextControlElement::VisiblePositionForIndex(int index) const { + if (index <= 0) + return VisiblePosition::FirstPositionInNode(*InnerEditorElement()); + Position start, end; + bool selected = Range::selectNodeContents(InnerEditorElement(), start, end); + if (!selected) + return VisiblePosition(); + CharacterIterator it(start, end); + it.Advance(index - 1); + return CreateVisiblePosition(it.EndPosition(), TextAffinity::kUpstream); +} + +// TODO(yosin): We should move |TextControlElement::indexForVisiblePosition()| +// to "AXLayoutObject.cpp" since this funciton is used only there. +int TextControlElement::IndexForVisiblePosition( + const VisiblePosition& pos) const { + Position index_position = pos.DeepEquivalent().ParentAnchoredEquivalent(); + if (EnclosingTextControl(index_position) != this) + return 0; + DCHECK(index_position.IsConnected()) << index_position; + return TextIterator::RangeLength(Position(InnerEditorElement(), 0), + index_position); +} + +unsigned TextControlElement::selectionStart() const { + if (!IsTextControl()) + return 0; + if (GetDocument().FocusedElement() != this) + return cached_selection_start_; + + return ComputeSelectionStart(); +} + +unsigned TextControlElement::ComputeSelectionStart() const { + DCHECK(IsTextControl()); + LocalFrame* frame = GetDocument().GetFrame(); + if (!frame) + return 0; + + // To avoid regression on speedometer benchmark[1] test, we should not + // update layout tree in this code block. + // [1] http://browserbench.org/Speedometer/ + DocumentLifecycle::DisallowTransitionScope disallow_transition( + GetDocument().Lifecycle()); + const SelectionInDOMTree& selection = + frame->Selection().GetSelectionInDOMTree(); + return IndexForPosition(InnerEditorElement(), + selection.ComputeStartPosition()); +} + +unsigned TextControlElement::selectionEnd() const { + if (!IsTextControl()) + return 0; + if (GetDocument().FocusedElement() != this) + return cached_selection_end_; + return ComputeSelectionEnd(); +} + +unsigned TextControlElement::ComputeSelectionEnd() const { + DCHECK(IsTextControl()); + LocalFrame* frame = GetDocument().GetFrame(); + if (!frame) + return 0; + + // To avoid regression on speedometer benchmark[1] test, we should not + // update layout tree in this code block. + // [1] http://browserbench.org/Speedometer/ + DocumentLifecycle::DisallowTransitionScope disallow_transition( + GetDocument().Lifecycle()); + const SelectionInDOMTree& selection = + frame->Selection().GetSelectionInDOMTree(); + return IndexForPosition(InnerEditorElement(), selection.ComputeEndPosition()); +} + +static const AtomicString& DirectionString( + TextFieldSelectionDirection direction) { + DEFINE_STATIC_LOCAL(const AtomicString, none, ("none")); + DEFINE_STATIC_LOCAL(const AtomicString, forward, ("forward")); + DEFINE_STATIC_LOCAL(const AtomicString, backward, ("backward")); + + switch (direction) { + case kSelectionHasNoDirection: + return none; + case kSelectionHasForwardDirection: + return forward; + case kSelectionHasBackwardDirection: + return backward; + } + + NOTREACHED(); + return none; +} + +const AtomicString& TextControlElement::selectionDirection() const { + // Ensured by HTMLInputElement::selectionDirectionForBinding(). + DCHECK(IsTextControl()); + if (GetDocument().FocusedElement() != this) + return DirectionString(cached_selection_direction_); + return DirectionString(ComputeSelectionDirection()); +} + +TextFieldSelectionDirection TextControlElement::ComputeSelectionDirection() + const { + DCHECK(IsTextControl()); + LocalFrame* frame = GetDocument().GetFrame(); + if (!frame) + return kSelectionHasNoDirection; + + // To avoid regression on speedometer benchmark[1] test, we should not + // update layout tree in this code block. + // [1] http://browserbench.org/Speedometer/ + DocumentLifecycle::DisallowTransitionScope disallow_transition( + GetDocument().Lifecycle()); + const SelectionInDOMTree& selection = + frame->Selection().GetSelectionInDOMTree(); + const Position& start = selection.ComputeStartPosition(); + return frame->Selection().IsDirectional() + ? (selection.Base() == start ? kSelectionHasForwardDirection + : kSelectionHasBackwardDirection) + : kSelectionHasNoDirection; +} + +static inline void SetContainerAndOffsetForRange(Node* node, + int offset, + Node*& container_node, + int& offset_in_container) { + if (node->IsTextNode()) { + container_node = node; + offset_in_container = offset; + } else { + container_node = node->parentNode(); + offset_in_container = node->NodeIndex() + offset; + } +} + +SelectionInDOMTree TextControlElement::Selection() const { + if (!GetLayoutObject() || !IsTextControl()) + return SelectionInDOMTree(); + + int start = cached_selection_start_; + int end = cached_selection_end_; + + DCHECK_LE(start, end); + HTMLElement* inner_text = InnerEditorElement(); + if (!inner_text) + return SelectionInDOMTree(); + + if (!inner_text->HasChildren()) { + return SelectionInDOMTree::Builder() + .Collapse(Position(inner_text, 0)) + .Build(); + } + + int offset = 0; + Node* start_node = nullptr; + Node* end_node = nullptr; + for (Node& node : NodeTraversal::DescendantsOf(*inner_text)) { + DCHECK(!node.hasChildren()); + DCHECK(node.IsTextNode() || IsHTMLBRElement(node)); + int length = node.IsTextNode() ? Position::LastOffsetInNode(node) : 1; + + if (offset <= start && start <= offset + length) + SetContainerAndOffsetForRange(&node, start - offset, start_node, start); + + if (offset <= end && end <= offset + length) { + SetContainerAndOffsetForRange(&node, end - offset, end_node, end); + break; + } + + offset += length; + } + + if (!start_node || !end_node) + return SelectionInDOMTree(); + + return SelectionInDOMTree::Builder() + .SetBaseAndExtent(Position(start_node, start), Position(end_node, end)) + .Build(); +} + +int TextControlElement::maxLength() const { + int value; + if (!ParseHTMLInteger(FastGetAttribute(maxlengthAttr), value)) + return -1; + return value >= 0 ? value : -1; +} + +int TextControlElement::minLength() const { + int value; + if (!ParseHTMLInteger(FastGetAttribute(minlengthAttr), value)) + return -1; + return value >= 0 ? value : -1; +} + +void TextControlElement::setMaxLength(int new_value, + ExceptionState& exception_state) { + int min = minLength(); + if (new_value < 0) { + exception_state.ThrowDOMException( + kIndexSizeError, "The value provided (" + String::Number(new_value) + + ") is not positive or 0."); + } else if (min >= 0 && new_value < min) { + exception_state.ThrowDOMException( + kIndexSizeError, ExceptionMessages::IndexExceedsMinimumBound( + "maxLength", new_value, min)); + } else { + SetIntegralAttribute(maxlengthAttr, new_value); + } +} + +void TextControlElement::setMinLength(int new_value, + ExceptionState& exception_state) { + int max = maxLength(); + if (new_value < 0) { + exception_state.ThrowDOMException( + kIndexSizeError, "The value provided (" + String::Number(new_value) + + ") is not positive or 0."); + } else if (max >= 0 && new_value > max) { + exception_state.ThrowDOMException( + kIndexSizeError, ExceptionMessages::IndexExceedsMaximumBound( + "minLength", new_value, max)); + } else { + SetIntegralAttribute(minlengthAttr, new_value); + } +} + +void TextControlElement::RestoreCachedSelection() { + if (SetSelectionRange(cached_selection_start_, cached_selection_end_, + cached_selection_direction_)) + ScheduleSelectEvent(); +} + +void TextControlElement::SelectionChanged(bool user_triggered) { + if (!GetLayoutObject() || !IsTextControl()) + return; + + // selectionStart() or selectionEnd() will return cached selection when this + // node doesn't have focus. + CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(), + ComputeSelectionDirection()); + + LocalFrame* frame = GetDocument().GetFrame(); + if (!frame || !user_triggered) + return; + const SelectionInDOMTree& selection = + frame->Selection().GetSelectionInDOMTree(); + if (selection.Type() != kRangeSelection) + return; + DispatchEvent(Event::CreateBubble(EventTypeNames::select)); +} + +void TextControlElement::ScheduleSelectEvent() { + Event* event = Event::CreateBubble(EventTypeNames::select); + event->SetTarget(this); + GetDocument().EnqueueAnimationFrameEvent(event); +} + +void TextControlElement::ParseAttribute( + const AttributeModificationParams& params) { + if (params.name == placeholderAttr) { + UpdatePlaceholderText(); + UpdatePlaceholderVisibility(); + UseCounter::Count(GetDocument(), WebFeature::kPlaceholderAttribute); + } else { + HTMLFormControlElementWithState::ParseAttribute(params); + } +} + +bool TextControlElement::LastChangeWasUserEdit() const { + if (!IsTextControl()) + return false; + return last_change_was_user_edit_; +} + +Node* TextControlElement::CreatePlaceholderBreakElement() const { + return HTMLBRElement::Create(GetDocument()); +} + +void TextControlElement::AddPlaceholderBreakElementIfNecessary() { + HTMLElement* inner_editor = InnerEditorElement(); + if (inner_editor->GetLayoutObject() && + !inner_editor->GetLayoutObject()->Style()->PreserveNewline()) + return; + Node* last_child = inner_editor->lastChild(); + if (!last_child || !last_child->IsTextNode()) + return; + if (ToText(last_child)->data().EndsWith('\n') || + ToText(last_child)->data().EndsWith('\r')) + inner_editor->AppendChild(CreatePlaceholderBreakElement()); +} + +void TextControlElement::SetInnerEditorValue(const String& value) { + DCHECK(!OpenShadowRoot()); + if (!IsTextControl() || OpenShadowRoot()) + return; + + DCHECK(InnerEditorElement()); + + bool text_is_changed = value != InnerEditorValue(); + HTMLElement* inner_editor = InnerEditorElement(); + if (!text_is_changed && inner_editor->HasChildren()) + return; + + // If the last child is a trailing <br> that's appended below, remove it + // first so as to enable setInnerText() fast path of updating a text node. + if (IsHTMLBRElement(inner_editor->lastChild())) + inner_editor->RemoveChild(inner_editor->lastChild(), ASSERT_NO_EXCEPTION); + + // We don't use setTextContent. It triggers unnecessary paint. + if (value.IsEmpty()) + inner_editor->RemoveChildren(); + else + ReplaceChildrenWithText(inner_editor, value, ASSERT_NO_EXCEPTION); + + // Add <br> so that we can put the caret at the next line of the last + // newline. + AddPlaceholderBreakElementIfNecessary(); + + if (text_is_changed && GetLayoutObject()) { + if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) + cache->HandleTextFormControlChanged(this); + } +} + +String TextControlElement::InnerEditorValue() const { + DCHECK(!OpenShadowRoot()); + HTMLElement* inner_editor = InnerEditorElement(); + if (!inner_editor || !IsTextControl()) + return g_empty_string; + + // Typically, innerEditor has 0 or one Text node followed by 0 or one <br>. + if (!inner_editor->HasChildren()) + return g_empty_string; + Node& first_child = *inner_editor->firstChild(); + if (first_child.IsTextNode()) { + Node* second_child = first_child.nextSibling(); + if (!second_child) + return ToText(first_child).data(); + if (!second_child->nextSibling() && IsHTMLBRElement(*second_child)) + return ToText(first_child).data(); + } else if (!first_child.nextSibling() && IsHTMLBRElement(first_child)) { + return g_empty_string; + } + + StringBuilder result; + for (Node& node : NodeTraversal::InclusiveDescendantsOf(*inner_editor)) { + if (IsHTMLBRElement(node)) { + DCHECK_EQ(&node, inner_editor->lastChild()); + if (&node != inner_editor->lastChild()) + result.Append(kNewlineCharacter); + } else if (node.IsTextNode()) { + result.Append(ToText(node).data()); + } + } + return result.ToString(); +} + +static void GetNextSoftBreak(RootInlineBox*& line, + Node*& break_node, + unsigned& break_offset) { + RootInlineBox* next; + for (; line; line = next) { + next = line->NextRootBox(); + if (next && !line->EndsWithBreak()) { + DCHECK(line->LineBreakObj()); + break_node = line->LineBreakObj().GetNode(); + break_offset = line->LineBreakPos(); + line = next; + return; + } + } + break_node = nullptr; + break_offset = 0; +} + +String TextControlElement::ValueWithHardLineBreaks() const { + // FIXME: It's not acceptable to ignore the HardWrap setting when there is no + // layoutObject. While we have no evidence this has ever been a practical + // problem, it would be best to fix it some day. + HTMLElement* inner_text = InnerEditorElement(); + if (!inner_text || !IsTextControl()) + return value(); + + LayoutBlockFlow* layout_object = + ToLayoutBlockFlow(inner_text->GetLayoutObject()); + if (!layout_object) + return value(); + + DCHECK(CanUseInlineBox(*layout_object)); + Node* break_node; + unsigned break_offset; + RootInlineBox* line = layout_object->FirstRootBox(); + if (!line) + return value(); + + GetNextSoftBreak(line, break_node, break_offset); + + StringBuilder result; + for (Node& node : NodeTraversal::DescendantsOf(*inner_text)) { + if (IsHTMLBRElement(node)) { + DCHECK_EQ(&node, inner_text->lastChild()); + if (&node != inner_text->lastChild()) + result.Append(kNewlineCharacter); + } else if (node.IsTextNode()) { + String data = ToText(node).data(); + unsigned length = data.length(); + unsigned position = 0; + while (break_node == node && break_offset <= length) { + if (break_offset > position) { + result.Append(data, position, break_offset - position); + position = break_offset; + result.Append(kNewlineCharacter); + } + GetNextSoftBreak(line, break_node, break_offset); + } + result.Append(data, position, length - position); + } + while (break_node == node) + GetNextSoftBreak(line, break_node, break_offset); + } + return result.ToString(); +} + +TextControlElement* EnclosingTextControl(const Position& position) { + DCHECK(position.IsNull() || position.IsOffsetInAnchor() || + position.ComputeContainerNode() || + !position.AnchorNode()->OwnerShadowHost() || + (position.AnchorNode()->parentNode() && + position.AnchorNode()->parentNode()->IsShadowRoot())); + return EnclosingTextControl(position.ComputeContainerNode()); +} + +TextControlElement* EnclosingTextControl(const Node* container) { + if (!container) + return nullptr; + Element* ancestor = container->OwnerShadowHost(); + return ancestor && IsTextControl(*ancestor) && + container->ContainingShadowRoot()->IsUserAgent() + ? ToTextControl(ancestor) + : nullptr; +} + +String TextControlElement::DirectionForFormData() const { + for (const HTMLElement* element = this; element; + element = Traversal<HTMLElement>::FirstAncestor(*element)) { + const AtomicString& dir_attribute_value = + element->FastGetAttribute(dirAttr); + if (dir_attribute_value.IsNull()) + continue; + + if (DeprecatedEqualIgnoringCase(dir_attribute_value, "rtl") || + DeprecatedEqualIgnoringCase(dir_attribute_value, "ltr")) + return dir_attribute_value; + + if (DeprecatedEqualIgnoringCase(dir_attribute_value, "auto")) { + bool is_auto; + TextDirection text_direction = + element->DirectionalityIfhasDirAutoAttribute(is_auto); + return text_direction == TextDirection::kRtl ? "rtl" : "ltr"; + } + } + + return "ltr"; +} + +void TextControlElement::SetAutofillValue(const String& value) { + // Set the value trimmed to the max length of the field and dispatch the input + // and change events. + setValue(value.Substring(0, maxLength()), kDispatchInputAndChangeEvent); +} + +// TODO(crbug.com/772433): Create and use a new suggested-value element instead. +void TextControlElement::SetSuggestedValue(const String& value) { + suggested_value_ = value.Substring(0, maxLength()); + if (!suggested_value_.IsEmpty() && !InnerEditorValue().IsEmpty()) { + // If there is an inner editor value, hide it so the suggested value can be + // shown to the user. + static_cast<TextControlInnerEditorElement*>(InnerEditorElement()) + ->SetVisibility(false); + } else if (suggested_value_.IsEmpty() && InnerEditorElement()) { + // If there is no suggested value and there is an InnerEditorElement, reset + // its visibility. + static_cast<TextControlInnerEditorElement*>(InnerEditorElement()) + ->SetVisibility(true); + } + + UpdatePlaceholderText(); + + HTMLElement* placeholder = PlaceholderElement(); + if (!placeholder) + return; + + UpdatePlaceholderVisibility(); + + if (suggested_value_.IsEmpty()) { + // Reset the pseudo-id for placeholders to use the appropriated style + placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder")); + } else { + // Set the pseudo-id for suggested values to use the appropriated style. + placeholder->SetShadowPseudoId(AtomicString("-internal-input-suggested")); + } +} + +HTMLElement* TextControlElement::CreateInnerEditorElement() { + DCHECK(!inner_editor_); + inner_editor_ = TextControlInnerEditorElement::Create(GetDocument()); + return inner_editor_; +} + +const String& TextControlElement::SuggestedValue() const { + return suggested_value_; +} + +void TextControlElement::Trace(Visitor* visitor) { + visitor->Trace(inner_editor_); + HTMLFormControlElementWithState::Trace(visitor); +} + +void TextControlElement::CloneNonAttributePropertiesFrom( + const Element& source, + CloneChildrenFlag flag) { + const TextControlElement& source_element = + static_cast<const TextControlElement&>(source); + last_change_was_user_edit_ = source_element.last_change_was_user_edit_; + HTMLFormControlElement::CloneNonAttributePropertiesFrom(source, flag); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h new file mode 100644 index 00000000000..e7a9ce1cb34 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element.h @@ -0,0 +1,265 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights + * reserved. + * Copyright (C) 2009, 2010, 2011 Google Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_ELEMENT_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_ELEMENT_H_ + +#include "base/gtest_prod_util.h" +#include "third_party/blink/public/platform/web_focus_type.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/editing/forward.h" +#include "third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.h" + +namespace blink { + +class ExceptionState; + +enum TextFieldSelectionDirection { + kSelectionHasNoDirection, + kSelectionHasForwardDirection, + kSelectionHasBackwardDirection +}; +enum TextFieldEventBehavior { + kDispatchNoEvent, + kDispatchChangeEvent, + kDispatchInputAndChangeEvent +}; + +enum class TextControlSetValueSelection { + kSetSelectionToEnd, + kDoNotSet, +}; + +class CORE_EXPORT TextControlElement : public HTMLFormControlElementWithState { + public: + // Common flag for HTMLInputElement::tooLong(), + // HTMLTextAreaElement::tooLong(), + // HTMLInputElement::tooShort() and HTMLTextAreaElement::tooShort(). + enum NeedsToCheckDirtyFlag { kCheckDirtyFlag, kIgnoreDirtyFlag }; + + ~TextControlElement() override; + + void ForwardEvent(Event*); + + void SetFocused(bool, WebFocusType) override; + + // The derived class should return true if placeholder processing is needed. + virtual bool IsPlaceholderVisible() const = 0; + virtual void SetPlaceholderVisibility(bool) = 0; + virtual bool SupportsPlaceholder() const = 0; + String StrippedPlaceholder() const; + HTMLElement* PlaceholderElement() const; + void UpdatePlaceholderVisibility(); + + VisiblePosition VisiblePositionForIndex(int) const; + int IndexForVisiblePosition(const VisiblePosition&) const; + unsigned selectionStart() const; + unsigned selectionEnd() const; + const AtomicString& selectionDirection() const; + void setSelectionStart(unsigned); + void setSelectionEnd(unsigned); + void setSelectionDirection(const String&); + void select(); + virtual void setRangeText(const String& replacement, ExceptionState&); + virtual void setRangeText(const String& replacement, + unsigned start, + unsigned end, + const String& selection_mode, + ExceptionState&); + // Web-exposed setSelectionRange() function. This schedule to dispatch + // 'select' event. + void setSelectionRangeForBinding(unsigned start, + unsigned end, + const String& direction = "none"); + // Blink-internal version of setSelectionRange(). This translates "none" + // direction to "forward" on platforms without "none" direction. + // This returns true if it updated cached selection and/or FrameSelection. + bool SetSelectionRange( + unsigned start, + unsigned end, + TextFieldSelectionDirection = kSelectionHasNoDirection); + SelectionInDOMTree Selection() const; + + int maxLength() const; + int minLength() const; + void setMaxLength(int, ExceptionState&); + void setMinLength(int, ExceptionState&); + + // Dispatch 'change' event if the value is updated. + void DispatchFormControlChangeEvent(); + // Enqueue 'change' event if the value is updated. + void EnqueueChangeEvent(); + // This should be called on every user-input, before the user-input changes + // the value. + void SetValueBeforeFirstUserEditIfNotSet(); + // This should be called on every user-input, after the user-input changed the + // value. The argument is the updated value. + void CheckIfValueWasReverted(const String&); + void ClearValueBeforeFirstUserEdit(); + + virtual String value() const = 0; + virtual void setValue( + const String&, + TextFieldEventBehavior = kDispatchNoEvent, + TextControlSetValueSelection = + TextControlSetValueSelection::kSetSelectionToEnd) = 0; + + HTMLElement* InnerEditorElement() const { return inner_editor_; } + HTMLElement* CreateInnerEditorElement(); + void DropInnerEditorElement() { inner_editor_ = nullptr; } + + void SelectionChanged(bool user_triggered); + bool LastChangeWasUserEdit() const; + virtual void SetInnerEditorValue(const String&); + String InnerEditorValue() const; + Node* CreatePlaceholderBreakElement() const; + + String DirectionForFormData() const; + + // Set the value trimmed to the max length of the field and dispatch the input + // and change events. + void SetAutofillValue(const String& value); + + virtual void SetSuggestedValue(const String& value); + const String& SuggestedValue() const; + + void Trace(Visitor*) override; + + protected: + TextControlElement(const QualifiedName&, Document&); + bool IsPlaceholderEmpty() const; + virtual void UpdatePlaceholderText() = 0; + virtual String GetPlaceholderValue() const = 0; + + void ParseAttribute(const AttributeModificationParams&) override; + + void RestoreCachedSelection(); + + void DefaultEventHandler(Event*) override; + virtual void SubtreeHasChanged() = 0; + + void SetLastChangeWasNotUserEdit() { last_change_was_user_edit_ = false; } + void AddPlaceholderBreakElementIfNecessary(); + String ValueWithHardLineBreaks() const; + + void CloneNonAttributePropertiesFrom(const Element&, + CloneChildrenFlag) override; + + private: + unsigned ComputeSelectionStart() const; + unsigned ComputeSelectionEnd() const; + TextFieldSelectionDirection ComputeSelectionDirection() const; + // Returns true if cached values and arguments are not same. + bool CacheSelection(unsigned start, + unsigned end, + TextFieldSelectionDirection); + static unsigned IndexForPosition(HTMLElement* inner_editor, const Position&); + + void DispatchFocusEvent(Element* old_focused_element, + WebFocusType, + InputDeviceCapabilities* source_capabilities) final; + void DispatchBlurEvent(Element* new_focused_element, + WebFocusType, + InputDeviceCapabilities* source_capabilities) final; + void ScheduleSelectEvent(); + + // Returns true if user-editable value is empty. Used to check placeholder + // visibility. + virtual bool IsEmptyValue() const = 0; + // Returns true if suggested value is empty. Used to check placeholder + // visibility. + bool IsEmptySuggestedValue() const { return SuggestedValue().IsEmpty(); } + // Called in dispatchFocusEvent(), after placeholder process, before calling + // parent's dispatchFocusEvent(). + virtual void HandleFocusEvent(Element* /* oldFocusedNode */, WebFocusType) {} + // Called in dispatchBlurEvent(), after placeholder process, before calling + // parent's dispatchBlurEvent(). + virtual void HandleBlurEvent() {} + + // Whether the placeholder attribute value should be visible. Does not + // necessarily match the placeholder_element visibility because it can be used + // for suggested values too. + bool PlaceholderShouldBeVisible() const; + + // Held directly instead of looked up by ID for speed. + // Not only is the lookup faster, but for simple text inputs it avoids + // creating a number of TreeScope data structures to track elements by ID. + Member<HTMLElement> inner_editor_; + + // In m_valueBeforeFirstUserEdit, we distinguish a null String and zero-length + // String. Null String means the field doesn't have any data yet, and + // zero-length String is a valid data. + String value_before_first_user_edit_; + bool last_change_was_user_edit_; + + unsigned cached_selection_start_; + unsigned cached_selection_end_; + TextFieldSelectionDirection cached_selection_direction_; + + String suggested_value_; + String value_before_set_suggested_value_; + + FRIEND_TEST_ALL_PREFIXES(TextControlElementTest, IndexForPosition); +}; + +inline bool IsTextControl(const Node& node) { + return node.IsElementNode() && ToElement(node).IsTextControl(); +} +inline bool IsTextControl(const Node* node) { + return node && IsTextControl(*node); +} + +// We can't use DEFINE_TYPE_CASTS for TextControl because macro +// names and the destination type name are not matched. +// e.g. ToTextControl() returns TextControlElement. +#define DEFINE_TEXT_CONTROL_CASTS(Type, ArgType) \ + inline Type* ToTextControl(ArgType* node) { \ + SECURITY_DCHECK(!node || IsTextControl(*node)); \ + return static_cast<Type*>(node); \ + } \ + inline Type& ToTextControl(ArgType& node) { \ + SECURITY_DCHECK(IsTextControl(node)); \ + return static_cast<Type&>(node); \ + } \ + inline Type* ToTextControlOrNull(ArgType* node) { \ + return node && IsTextControl(*node) ? static_cast<Type*>(node) : nullptr; \ + } \ + inline Type* ToTextControlOrNull(ArgType& node) { \ + return IsTextControl(node) ? static_cast<Type*>(&node) : nullptr; \ + } \ + void ToTextControl(Type*); \ + void ToTextControl(Type&) + +DEFINE_TEXT_CONTROL_CASTS(TextControlElement, Node); +DEFINE_TEXT_CONTROL_CASTS(const TextControlElement, const Node); + +#undef DEFINE_TEXT_CONTROL_CASTS + +TextControlElement* EnclosingTextControl(const Position&); +TextControlElement* EnclosingTextControl(const Node*); + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc b/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc new file mode 100644 index 00000000000..e3f8b036562 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_element_test.cc @@ -0,0 +1,90 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/html/forms/text_control_element.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/editing/position.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" +#include "third_party/blink/renderer/core/loader/empty_clients.h" +#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" + +namespace blink { + +class TextControlElementTest : public testing::Test { + protected: + void SetUp() override; + + DummyPageHolder& Page() const { return *dummy_page_holder_; } + Document& GetDocument() const { return *document_; } + TextControlElement& TextControl() const { return *text_control_; } + HTMLInputElement& Input() const { return *input_; } + + private: + std::unique_ptr<DummyPageHolder> dummy_page_holder_; + + Persistent<Document> document_; + Persistent<TextControlElement> text_control_; + Persistent<HTMLInputElement> input_; +}; + +void TextControlElementTest::SetUp() { + Page::PageClients page_clients; + FillWithEmptyClients(page_clients); + dummy_page_holder_ = + DummyPageHolder::Create(IntSize(800, 600), &page_clients); + + document_ = &dummy_page_holder_->GetDocument(); + document_->documentElement()->SetInnerHTMLFromString( + "<body><textarea id=textarea></textarea><input id=input /></body>"); + document_->View()->UpdateAllLifecyclePhases(); + text_control_ = ToTextControl(document_->getElementById("textarea")); + text_control_->focus(); + input_ = ToHTMLInputElement(document_->getElementById("input")); +} + +TEST_F(TextControlElementTest, SetSelectionRange) { + EXPECT_EQ(0u, TextControl().selectionStart()); + EXPECT_EQ(0u, TextControl().selectionEnd()); + + TextControl().SetInnerEditorValue("Hello, text form."); + EXPECT_EQ(0u, TextControl().selectionStart()); + EXPECT_EQ(0u, TextControl().selectionEnd()); + + TextControl().SetSelectionRange(1, 3); + EXPECT_EQ(1u, TextControl().selectionStart()); + EXPECT_EQ(3u, TextControl().selectionEnd()); +} + +TEST_F(TextControlElementTest, SetSelectionRangeDoesNotCauseLayout) { + Input().focus(); + Input().setValue("Hello, input form."); + Input().SetSelectionRange(1, 1); + + // Force layout if document().updateStyleAndLayoutIgnorePendingStylesheets() + // is called. + GetDocument().body()->AppendChild(GetDocument().createTextNode("foo")); + const int start_layout_count = Page().GetFrameView().LayoutCount(); + EXPECT_TRUE(GetDocument().NeedsLayoutTreeUpdate()); + Input().SetSelectionRange(2, 2); + EXPECT_EQ(start_layout_count, Page().GetFrameView().LayoutCount()); +} + +TEST_F(TextControlElementTest, IndexForPosition) { + HTMLInputElement* input = + ToHTMLInputElement(GetDocument().getElementById("input")); + input->setValue("Hello"); + HTMLElement* inner_editor = input->InnerEditorElement(); + EXPECT_EQ(5u, TextControlElement::IndexForPosition( + inner_editor, + Position(inner_editor, PositionAnchorType::kAfterAnchor))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc new file mode 100644 index 00000000000..90890d15156 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h" + +#include "third_party/blink/renderer/core/css/resolver/style_adjuster.h" +#include "third_party/blink/renderer/core/css/style_change_reason.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" +#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h" +#include "third_party/blink/renderer/core/events/mouse_event.h" +#include "third_party/blink/renderer/core/events/text_event.h" +#include "third_party/blink/renderer/core/events/text_event_input_type.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/layout/layout_text_control_single_line.h" + +namespace blink { + +using namespace HTMLNames; + +TextControlInnerContainer::TextControlInnerContainer(Document& document) + : HTMLDivElement(document) {} + +TextControlInnerContainer* TextControlInnerContainer::Create( + Document& document) { + TextControlInnerContainer* element = new TextControlInnerContainer(document); + element->setAttribute(idAttr, ShadowElementNames::TextFieldContainer()); + return element; +} + +LayoutObject* TextControlInnerContainer::CreateLayoutObject( + const ComputedStyle&) { + return new LayoutTextControlInnerContainer(this); +} + +// --------------------------- + +EditingViewPortElement::EditingViewPortElement(Document& document) + : HTMLDivElement(document) { + SetHasCustomStyleCallbacks(); +} + +EditingViewPortElement* EditingViewPortElement::Create(Document& document) { + EditingViewPortElement* element = new EditingViewPortElement(document); + element->setAttribute(idAttr, ShadowElementNames::EditingViewPort()); + return element; +} + +scoped_refptr<ComputedStyle> +EditingViewPortElement::CustomStyleForLayoutObject() { + // FXIME: Move these styles to html.css. + + scoped_refptr<ComputedStyle> style = ComputedStyle::Create(); + style->InheritFrom(OwnerShadowHost()->ComputedStyleRef()); + + style->SetFlexGrow(1); + style->SetMinWidth(Length(0, kFixed)); + style->SetDisplay(EDisplay::kBlock); + style->SetDirection(TextDirection::kLtr); + + // We don't want the shadow dom to be editable, so we set this block to + // read-only in case the input itself is editable. + style->SetUserModify(EUserModify::kReadOnly); + style->SetUnique(); + + return style; +} + +// --------------------------- + +inline TextControlInnerEditorElement::TextControlInnerEditorElement( + Document& document) + : HTMLDivElement(document) { + SetHasCustomStyleCallbacks(); +} + +TextControlInnerEditorElement* TextControlInnerEditorElement::Create( + Document& document) { + return new TextControlInnerEditorElement(document); +} + +void TextControlInnerEditorElement::DefaultEventHandler(Event* event) { + // FIXME: In the future, we should add a way to have default event listeners. + // Then we would add one to the text field's inner div, and we wouldn't need + // this subclass. + // Or possibly we could just use a normal event listener. + if (event->IsBeforeTextInsertedEvent() || + event->type() == EventTypeNames::webkitEditableContentChanged) { + Element* shadow_ancestor = OwnerShadowHost(); + // A TextControlInnerTextElement can have no host if its been detached, + // but kept alive by an EditCommand. In this case, an undo/redo can + // cause events to be sent to the TextControlInnerTextElement. To + // prevent an infinite loop, we must check for this case before sending + // the event up the chain. + if (shadow_ancestor) + shadow_ancestor->DefaultEventHandler(event); + } + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); +} + +void TextControlInnerEditorElement::SetVisibility(bool is_visible) { + if (is_visible_ != is_visible) { + is_visible_ = is_visible; + SetNeedsStyleRecalc( + kLocalStyleChange, + StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue)); + } +} + +LayoutObject* TextControlInnerEditorElement::CreateLayoutObject( + const ComputedStyle&) { + return new LayoutTextControlInnerEditor(this); +} + +scoped_refptr<ComputedStyle> +TextControlInnerEditorElement::CustomStyleForLayoutObject() { + LayoutObject* parent_layout_object = OwnerShadowHost()->GetLayoutObject(); + if (!parent_layout_object || !parent_layout_object->IsTextControl()) + return OriginalStyleForLayoutObject(); + LayoutTextControl* text_control = ToLayoutTextControl(parent_layout_object); + scoped_refptr<ComputedStyle> inner_editor_style = + text_control->CreateInnerEditorStyle(text_control->StyleRef()); + // Using StyleAdjuster::adjustComputedStyle updates unwanted style. We'd like + // to apply only editing-related and alignment-related. + StyleAdjuster::AdjustStyleForEditing(*inner_editor_style); + if (!is_visible_) + inner_editor_style->SetOpacity(0); + return inner_editor_style; +} + +// ---------------------------- + +inline SearchFieldCancelButtonElement::SearchFieldCancelButtonElement( + Document& document) + : HTMLDivElement(document), capturing_(false) {} + +SearchFieldCancelButtonElement* SearchFieldCancelButtonElement::Create( + Document& document) { + SearchFieldCancelButtonElement* element = + new SearchFieldCancelButtonElement(document); + element->SetShadowPseudoId(AtomicString("-webkit-search-cancel-button")); + element->setAttribute(idAttr, ShadowElementNames::SearchClearButton()); + return element; +} + +void SearchFieldCancelButtonElement::DetachLayoutTree( + const AttachContext& context) { + if (capturing_) { + if (LocalFrame* frame = GetDocument().GetFrame()) + frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr); + } + HTMLDivElement::DetachLayoutTree(context); +} + +void SearchFieldCancelButtonElement::DefaultEventHandler(Event* event) { + // If the element is visible, on mouseup, clear the value, and set selection + HTMLInputElement* input(ToHTMLInputElement(OwnerShadowHost())); + if (!input || input->IsDisabledOrReadOnly()) { + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); + return; + } + + if (event->type() == EventTypeNames::click && event->IsMouseEvent() && + ToMouseEvent(event)->button() == + static_cast<short>(WebPointerProperties::Button::kLeft)) { + input->SetValueForUser(""); + input->SetAutofilled(false); + input->OnSearch(); + event->SetDefaultHandled(); + } + + if (!event->DefaultHandled()) + HTMLDivElement::DefaultEventHandler(event); +} + +bool SearchFieldCancelButtonElement::WillRespondToMouseClickEvents() { + const HTMLInputElement* input = ToHTMLInputElement(OwnerShadowHost()); + if (input && !input->IsDisabledOrReadOnly()) + return true; + + return HTMLDivElement::WillRespondToMouseClickEvents(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h new file mode 100644 index 00000000000..5f448b6b052 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_control_inner_elements.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_INNER_ELEMENTS_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_CONTROL_INNER_ELEMENTS_H_ + +#include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/platform/wtf/forward.h" + +namespace blink { + +class TextControlInnerContainer final : public HTMLDivElement { + public: + static TextControlInnerContainer* Create(Document&); + + protected: + explicit TextControlInnerContainer(Document&); + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; +}; + +class EditingViewPortElement final : public HTMLDivElement { + public: + static EditingViewPortElement* Create(Document&); + + protected: + explicit EditingViewPortElement(Document&); + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override; + + private: + bool SupportsFocus() const override { return false; } +}; + +class TextControlInnerEditorElement final : public HTMLDivElement { + public: + static TextControlInnerEditorElement* Create(Document&); + + void DefaultEventHandler(Event*) override; + + void SetVisibility(bool is_visible); + + private: + explicit TextControlInnerEditorElement(Document&); + LayoutObject* CreateLayoutObject(const ComputedStyle&) override; + scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override; + bool SupportsFocus() const override { return false; } + bool is_visible_ = true; +}; + +class SearchFieldCancelButtonElement final : public HTMLDivElement { + public: + static SearchFieldCancelButtonElement* Create(Document&); + + void DefaultEventHandler(Event*) override; + bool WillRespondToMouseClickEvents() override; + + private: + explicit SearchFieldCancelButtonElement(Document&); + void DetachLayoutTree(const AttachContext& = AttachContext()) override; + bool SupportsFocus() const override { return false; } + + bool capturing_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc new file mode 100644 index 00000000000..c5e2fbf13e6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.cc @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/text_field_input_type.h" + +#include "third_party/blink/renderer/bindings/core/v8/exception_state.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" +#include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/events/before_text_inserted_event.h" +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/core/events/text_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/forms/form_data.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h" +#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/layout/layout_details_marker.h" +#include "third_party/blink/renderer/core/layout/layout_text_control_single_line.h" +#include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/page/chrome_client.h" +#include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/paint/paint_layer.h" +#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using namespace HTMLNames; + +class DataListIndicatorElement final : public HTMLDivElement { + private: + inline DataListIndicatorElement(Document& document) + : HTMLDivElement(document) {} + inline HTMLInputElement* HostInput() const { + return ToHTMLInputElement(OwnerShadowHost()); + } + + LayoutObject* CreateLayoutObject(const ComputedStyle&) override { + return new LayoutDetailsMarker(this); + } + + EventDispatchHandlingState* PreDispatchEventHandler(Event* event) override { + // Chromium opens autofill popup in a mousedown event listener + // associated to the document. We don't want to open it in this case + // because we opens a datalist chooser later. + // FIXME: We should dispatch mousedown events even in such case. + if (event->type() == EventTypeNames::mousedown) + event->stopPropagation(); + return nullptr; + } + + void DefaultEventHandler(Event* event) override { + DCHECK(GetDocument().IsActive()); + if (event->type() != EventTypeNames::click) + return; + HTMLInputElement* host = HostInput(); + if (host && !host->IsDisabledOrReadOnly()) { + GetDocument().GetPage()->GetChromeClient().OpenTextDataListChooser(*host); + event->SetDefaultHandled(); + } + } + + bool WillRespondToMouseClickEvents() override { + return HostInput() && !HostInput()->IsDisabledOrReadOnly() && + GetDocument().IsActive(); + } + + public: + static DataListIndicatorElement* Create(Document& document) { + DataListIndicatorElement* element = new DataListIndicatorElement(document); + element->SetShadowPseudoId( + AtomicString("-webkit-calendar-picker-indicator")); + element->setAttribute(idAttr, ShadowElementNames::PickerIndicator()); + return element; + } +}; + +TextFieldInputType::TextFieldInputType(HTMLInputElement& element) + : InputType(element), InputTypeView(element) {} + +TextFieldInputType::~TextFieldInputType() = default; + +void TextFieldInputType::Trace(blink::Visitor* visitor) { + InputTypeView::Trace(visitor); + InputType::Trace(visitor); +} + +InputTypeView* TextFieldInputType::CreateView() { + return this; +} + +InputType::ValueMode TextFieldInputType::GetValueMode() const { + return ValueMode::kValue; +} + +SpinButtonElement* TextFieldInputType::GetSpinButtonElement() const { + return ToSpinButtonElementOrDie( + GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::SpinButton())); +} + +bool TextFieldInputType::ShouldShowFocusRingOnMouseFocus() const { + return true; +} + +bool TextFieldInputType::IsTextField() const { + return true; +} + +bool TextFieldInputType::ValueMissing(const String& value) const { + return GetElement().IsRequired() && value.IsEmpty(); +} + +bool TextFieldInputType::CanSetSuggestedValue() { + return true; +} + +void TextFieldInputType::SetValue(const String& sanitized_value, + bool value_changed, + TextFieldEventBehavior event_behavior, + TextControlSetValueSelection selection) { + // We don't use InputType::setValue. TextFieldInputType dispatches events + // different way from InputType::setValue. + if (event_behavior == kDispatchNoEvent) + GetElement().SetNonAttributeValue(sanitized_value); + else + GetElement().SetNonAttributeValueByUserEdit(sanitized_value); + + // The following early-return can't be moved to the beginning of this + // function. We need to update non-attribute value even if the value is not + // changed. For example, <input type=number> has a badInput string, that is + // to say, IDL value=="", and new value is "", which should clear the badInput + // string and update validiity. + if (!value_changed) + return; + GetElement().UpdateView(); + + if (selection == TextControlSetValueSelection::kSetSelectionToEnd) { + unsigned max = VisibleValue().length(); + GetElement().SetSelectionRange(max, max); + } + + switch (event_behavior) { + case kDispatchChangeEvent: + // If the user is still editing this field, dispatch an input event rather + // than a change event. The change event will be dispatched when editing + // finishes. + if (GetElement().IsFocused()) + GetElement().DispatchInputEvent(); + else + GetElement().DispatchFormControlChangeEvent(); + break; + + case kDispatchInputAndChangeEvent: { + GetElement().DispatchInputEvent(); + GetElement().DispatchFormControlChangeEvent(); + break; + } + + case kDispatchNoEvent: + break; + } +} + +void TextFieldInputType::HandleKeydownEvent(KeyboardEvent* event) { + if (!GetElement().IsFocused()) + return; + if (ChromeClient* chrome_client = GetChromeClient()) { + chrome_client->HandleKeyboardEventOnTextField(GetElement(), *event); + return; + } + event->SetDefaultHandled(); +} + +void TextFieldInputType::HandleKeydownEventForSpinButton(KeyboardEvent* event) { + if (GetElement().IsDisabledOrReadOnly()) + return; + const String& key = event->key(); + if (key == "ArrowUp") + SpinButtonStepUp(); + else if (key == "ArrowDown" && !event->altKey()) + SpinButtonStepDown(); + else + return; + GetElement().DispatchFormControlChangeEvent(); + event->SetDefaultHandled(); +} + +void TextFieldInputType::ForwardEvent(Event* event) { + if (SpinButtonElement* spin_button = GetSpinButtonElement()) { + spin_button->ForwardEvent(event); + if (event->DefaultHandled()) + return; + } + + if (GetElement().GetLayoutObject() && + (event->IsMouseEvent() || event->IsDragEvent() || + event->HasInterface(EventNames::WheelEvent) || + event->type() == EventTypeNames::blur || + event->type() == EventTypeNames::focus)) { + LayoutTextControlSingleLine* layout_text_control = + ToLayoutTextControlSingleLine(GetElement().GetLayoutObject()); + if (event->type() == EventTypeNames::blur) { + if (LayoutBox* inner_editor_layout_object = + GetElement().InnerEditorElement()->GetLayoutBox()) { + // FIXME: This class has no need to know about PaintLayer! + if (PaintLayer* inner_layer = inner_editor_layout_object->Layer()) { + if (PaintLayerScrollableArea* inner_scrollable_area = + inner_layer->GetScrollableArea()) { + inner_scrollable_area->SetScrollOffset(ScrollOffset(0, 0), + kProgrammaticScroll); + } + } + } + + layout_text_control->CapsLockStateMayHaveChanged(); + } else if (event->type() == EventTypeNames::focus) { + layout_text_control->CapsLockStateMayHaveChanged(); + } + + GetElement().ForwardEvent(event); + } +} + +void TextFieldInputType::HandleBlurEvent() { + InputTypeView::HandleBlurEvent(); + GetElement().EndEditing(); + if (SpinButtonElement* spin_button = GetSpinButtonElement()) + spin_button->ReleaseCapture(); +} + +bool TextFieldInputType::ShouldSubmitImplicitly(Event* event) { + return (event->type() == EventTypeNames::textInput && + event->HasInterface(EventNames::TextEvent) && + ToTextEvent(event)->data() == "\n") || + InputTypeView::ShouldSubmitImplicitly(event); +} + +LayoutObject* TextFieldInputType::CreateLayoutObject( + const ComputedStyle&) const { + return new LayoutTextControlSingleLine(&GetElement()); +} + +bool TextFieldInputType::ShouldHaveSpinButton() const { + return LayoutTheme::GetTheme().ShouldHaveSpinButton(&GetElement()); +} + +void TextFieldInputType::CreateShadowSubtree() { + DCHECK(IsShadowHost(GetElement())); + ShadowRoot* shadow_root = GetElement().UserAgentShadowRoot(); + DCHECK(!shadow_root->HasChildren()); + + Document& document = GetElement().GetDocument(); + bool should_have_spin_button = ShouldHaveSpinButton(); + bool should_have_data_list_indicator = GetElement().HasValidDataListOptions(); + bool creates_container = should_have_spin_button || + should_have_data_list_indicator || NeedsContainer(); + + HTMLElement* inner_editor = GetElement().CreateInnerEditorElement(); + if (!creates_container) { + shadow_root->AppendChild(inner_editor); + return; + } + + TextControlInnerContainer* container = + TextControlInnerContainer::Create(document); + container->SetShadowPseudoId( + AtomicString("-webkit-textfield-decoration-container")); + shadow_root->AppendChild(container); + + EditingViewPortElement* editing_view_port = + EditingViewPortElement::Create(document); + editing_view_port->AppendChild(inner_editor); + container->AppendChild(editing_view_port); + + if (should_have_data_list_indicator) + container->AppendChild(DataListIndicatorElement::Create(document)); + // FIXME: Because of a special handling for a spin button in + // LayoutTextControlSingleLine, we need to put it to the last position. It's + // inconsistent with multiple-fields date/time types. + if (should_have_spin_button) + container->AppendChild(SpinButtonElement::Create(document, *this)); + + // See listAttributeTargetChanged too. +} + +Element* TextFieldInputType::ContainerElement() const { + return GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::TextFieldContainer()); +} + +void TextFieldInputType::DestroyShadowSubtree() { + InputTypeView::DestroyShadowSubtree(); + if (SpinButtonElement* spin_button = GetSpinButtonElement()) + spin_button->RemoveSpinButtonOwner(); +} + +void TextFieldInputType::ListAttributeTargetChanged() { + if (ChromeClient* chrome_client = GetChromeClient()) + chrome_client->TextFieldDataListChanged(GetElement()); + Element* picker = GetElement().UserAgentShadowRoot()->getElementById( + ShadowElementNames::PickerIndicator()); + bool did_have_picker_indicator = picker; + bool will_have_picker_indicator = GetElement().HasValidDataListOptions(); + if (did_have_picker_indicator == will_have_picker_indicator) + return; + EventDispatchForbiddenScope::AllowUserAgentEvents allow_events; + if (will_have_picker_indicator) { + Document& document = GetElement().GetDocument(); + if (Element* container = ContainerElement()) { + container->InsertBefore(DataListIndicatorElement::Create(document), + GetSpinButtonElement()); + } else { + // FIXME: The following code is similar to createShadowSubtree(), + // but they are different. We should simplify the code by making + // containerElement mandatory. + Element* rp_container = TextControlInnerContainer::Create(document); + rp_container->SetShadowPseudoId( + AtomicString("-webkit-textfield-decoration-container")); + Element* inner_editor = GetElement().InnerEditorElement(); + inner_editor->parentNode()->ReplaceChild(rp_container, inner_editor); + Element* editing_view_port = EditingViewPortElement::Create(document); + editing_view_port->AppendChild(inner_editor); + rp_container->AppendChild(editing_view_port); + rp_container->AppendChild(DataListIndicatorElement::Create(document)); + if (GetElement().GetDocument().FocusedElement() == GetElement()) + GetElement().UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore); + } + } else { + picker->remove(ASSERT_NO_EXCEPTION); + } +} + +void TextFieldInputType::AttributeChanged() { + // FIXME: Updating on any attribute update should be unnecessary. We should + // figure out what attributes affect. + UpdateView(); +} + +void TextFieldInputType::DisabledAttributeChanged() { + if (SpinButtonElement* spin_button = GetSpinButtonElement()) + spin_button->ReleaseCapture(); +} + +void TextFieldInputType::ReadonlyAttributeChanged() { + if (SpinButtonElement* spin_button = GetSpinButtonElement()) + spin_button->ReleaseCapture(); +} + +bool TextFieldInputType::SupportsReadOnly() const { + return true; +} + +static bool IsASCIILineBreak(UChar c) { + return c == '\r' || c == '\n'; +} + +static String LimitLength(const String& string, unsigned max_length) { + unsigned new_length = std::min(max_length, string.length()); + if (new_length == string.length()) + return string; + if (new_length > 0 && U16_IS_LEAD(string[new_length - 1])) + --new_length; + return string.Left(new_length); +} + +String TextFieldInputType::SanitizeValue(const String& proposed_value) const { + return LimitLength(proposed_value.RemoveCharacters(IsASCIILineBreak), + std::numeric_limits<int>::max()); +} + +void TextFieldInputType::HandleBeforeTextInsertedEvent( + BeforeTextInsertedEvent* event) { + // Make sure that the text to be inserted will not violate the maxLength. + + // We use HTMLInputElement::innerEditorValue() instead of + // HTMLInputElement::value() because they can be mismatched by + // sanitizeValue() in HTMLInputElement::subtreeHasChanged() in some cases. + unsigned old_length = GetElement().InnerEditorValue().length(); + + // selectionLength represents the selection length of this text field to be + // removed by this insertion. + // If the text field has no focus, we don't need to take account of the + // selection length. The selection is the source of text drag-and-drop in + // that case, and nothing in the text field will be removed. + unsigned selection_length = 0; + if (GetElement().IsFocused()) { + // TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets + // needs to be audited. See http://crbug.com/590369 for more details. + GetElement().GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + + selection_length = GetElement() + .GetDocument() + .GetFrame() + ->Selection() + .SelectedText() + .length(); + } + DCHECK_GE(old_length, selection_length); + + // Selected characters will be removed by the next text event. + unsigned base_length = old_length - selection_length; + unsigned max_length; + if (MaxLength() < 0) + max_length = std::numeric_limits<int>::max(); + else + max_length = static_cast<unsigned>(MaxLength()); + unsigned appendable_length = + max_length > base_length ? max_length - base_length : 0; + + // Truncate the inserted text to avoid violating the maxLength and other + // constraints. + String event_text = event->GetText(); + unsigned text_length = event_text.length(); + while (text_length > 0 && IsASCIILineBreak(event_text[text_length - 1])) + text_length--; + event_text.Truncate(text_length); + event_text.Replace("\r\n", " "); + event_text.Replace('\r', ' '); + event_text.Replace('\n', ' '); + + event->SetText(LimitLength(event_text, appendable_length)); +} + +bool TextFieldInputType::ShouldRespectListAttribute() { + return true; +} + +void TextFieldInputType::UpdatePlaceholderText() { + if (!SupportsPlaceholder()) + return; + HTMLElement* placeholder = GetElement().PlaceholderElement(); + String placeholder_text = GetElement().GetPlaceholderValue(); + if (placeholder_text.IsEmpty()) { + if (placeholder) + placeholder->remove(ASSERT_NO_EXCEPTION); + return; + } + if (!placeholder) { + HTMLElement* new_element = + HTMLDivElement::Create(GetElement().GetDocument()); + placeholder = new_element; + placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder")); + placeholder->SetInlineStyleProperty( + CSSPropertyDisplay, + GetElement().IsPlaceholderVisible() ? CSSValueBlock : CSSValueNone, + true); + placeholder->setAttribute(idAttr, ShadowElementNames::Placeholder()); + Element* container = ContainerElement(); + Node* previous = container ? container : GetElement().InnerEditorElement(); + previous->parentNode()->InsertBefore(placeholder, previous); + SECURITY_DCHECK(placeholder->parentNode() == previous->parentNode()); + } + placeholder->setTextContent(placeholder_text); +} + +void TextFieldInputType::AppendToFormData(FormData& form_data) const { + InputType::AppendToFormData(form_data); + const AtomicString& dirname_attr_value = + GetElement().FastGetAttribute(dirnameAttr); + if (!dirname_attr_value.IsNull()) + form_data.append(dirname_attr_value, GetElement().DirectionForFormData()); +} + +String TextFieldInputType::ConvertFromVisibleValue( + const String& visible_value) const { + return visible_value; +} + +void TextFieldInputType::SubtreeHasChanged() { + GetElement().SetValueFromRenderer(SanitizeUserInputValue( + ConvertFromVisibleValue(GetElement().InnerEditorValue()))); + GetElement().UpdatePlaceholderVisibility(); + GetElement().PseudoStateChanged(CSSSelector::kPseudoValid); + GetElement().PseudoStateChanged(CSSSelector::kPseudoInvalid); + GetElement().PseudoStateChanged(CSSSelector::kPseudoInRange); + GetElement().PseudoStateChanged(CSSSelector::kPseudoOutOfRange); + + DidSetValueByUserEdit(); +} + +void TextFieldInputType::DidSetValueByUserEdit() { + if (!GetElement().IsFocused()) + return; + if (ChromeClient* chrome_client = GetChromeClient()) + chrome_client->DidChangeValueInTextField(GetElement()); +} + +void TextFieldInputType::SpinButtonStepDown() { + StepUpFromLayoutObject(-1); +} + +void TextFieldInputType::SpinButtonStepUp() { + StepUpFromLayoutObject(1); +} + +void TextFieldInputType::UpdateView() { + if (GetElement().SuggestedValue().IsEmpty() && + GetElement().NeedsToUpdateViewValue()) { + // Update the view only if needsToUpdateViewValue is true. It protects + // an unacceptable view value from being overwritten with the DOM value. + // + // e.g. <input type=number> has a view value "abc", and input.max is + // updated. In this case, updateView() is called but we should not + // update the view value. + GetElement().SetInnerEditorValue(VisibleValue()); + GetElement().UpdatePlaceholderVisibility(); + } +} + +void TextFieldInputType::FocusAndSelectSpinButtonOwner() { + GetElement().focus(); + GetElement().SetSelectionRange(0, std::numeric_limits<int>::max()); +} + +bool TextFieldInputType::ShouldSpinButtonRespondToMouseEvents() { + return !GetElement().IsDisabledOrReadOnly(); +} + +bool TextFieldInputType::ShouldSpinButtonRespondToWheelEvents() { + return ShouldSpinButtonRespondToMouseEvents() && GetElement().IsFocused(); +} + +void TextFieldInputType::SpinButtonDidReleaseMouseCapture( + SpinButtonElement::EventDispatch event_dispatch) { + if (event_dispatch == SpinButtonElement::kEventDispatchAllowed) + GetElement().DispatchFormControlChangeEvent(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h new file mode 100644 index 00000000000..fe8ca8497ea --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_field_input_type.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_FIELD_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_FIELD_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/input_type.h" +#include "third_party/blink/renderer/core/html/forms/input_type_view.h" +#include "third_party/blink/renderer/core/html/forms/spin_button_element.h" + +namespace blink { + +// The class represents types of which UI contain text fields. +// It supports not only the types for BaseTextInputType but also type=number. +class TextFieldInputType : public InputType, + public InputTypeView, + protected SpinButtonElement::SpinButtonOwner { + USING_GARBAGE_COLLECTED_MIXIN(TextFieldInputType); + + public: + void Trace(blink::Visitor*) override; + using InputType::GetElement; + + protected: + TextFieldInputType(HTMLInputElement&); + ~TextFieldInputType() override; + bool CanSetSuggestedValue() override; + void HandleKeydownEvent(KeyboardEvent*) override; + + void CreateShadowSubtree() override; + void DestroyShadowSubtree() override; + void AttributeChanged() override; + void DisabledAttributeChanged() override; + void ReadonlyAttributeChanged() override; + bool SupportsReadOnly() const override; + void HandleBlurEvent() final; + String SanitizeValue(const String&) const override; + void SetValue(const String&, + bool value_changed, + TextFieldEventBehavior, + TextControlSetValueSelection) override; + void UpdateView() override; + LayoutObject* CreateLayoutObject(const ComputedStyle&) const override; + + virtual bool NeedsContainer() const { return false; } + virtual String ConvertFromVisibleValue(const String&) const; + virtual void DidSetValueByUserEdit(); + + void HandleKeydownEventForSpinButton(KeyboardEvent*); + bool ShouldHaveSpinButton() const; + Element* ContainerElement() const; + + private: + InputTypeView* CreateView() override; + ValueMode GetValueMode() const override; + bool ShouldShowFocusRingOnMouseFocus() const final; + bool IsTextField() const final; + bool ValueMissing(const String&) const override; + void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) override; + void ForwardEvent(Event*) final; + bool ShouldSubmitImplicitly(Event*) final; + bool ShouldRespectListAttribute() override; + void ListAttributeTargetChanged() override; + void UpdatePlaceholderText() final; + void AppendToFormData(FormData&) const override; + void SubtreeHasChanged() final; + + // SpinButtonElement::SpinButtonOwner functions. + void FocusAndSelectSpinButtonOwner() final; + bool ShouldSpinButtonRespondToMouseEvents() final; + bool ShouldSpinButtonRespondToWheelEvents() final; + void SpinButtonStepDown() final; + void SpinButtonStepUp() final; + void SpinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch) final; + + SpinButtonElement* GetSpinButtonElement() const; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_FIELD_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc new file mode 100644 index 00000000000..29847acfacd --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/text_input_type.h" + +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/input_type_names.h" + +namespace blink { + +using namespace HTMLNames; + +InputType* TextInputType::Create(HTMLInputElement& element) { + return new TextInputType(element); +} + +void TextInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeText); + if (GetElement().FastHasAttribute(maxlengthAttr)) + CountUsageIfVisible(WebFeature::kInputTypeTextMaxLength); + const AtomicString& type = GetElement().FastGetAttribute(typeAttr); + if (DeprecatedEqualIgnoringCase(type, InputTypeNames::datetime)) + CountUsageIfVisible(WebFeature::kInputTypeDateTimeFallback); + else if (DeprecatedEqualIgnoringCase(type, InputTypeNames::week)) + CountUsageIfVisible(WebFeature::kInputTypeWeekFallback); +} + +const AtomicString& TextInputType::FormControlType() const { + return InputTypeNames::text; +} + +bool TextInputType::SupportsInputModeAttribute() const { + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/text_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.h new file mode 100644 index 00000000000..012d79470f9 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/text_input_type.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h" + +namespace blink { + +class TextInputType final : public BaseTextInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + TextInputType(HTMLInputElement& element) : BaseTextInputType(element) {} + void CountUsage() override; + const AtomicString& FormControlType() const override; + bool SupportsInputModeAttribute() const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TEXT_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc new file mode 100644 index 00000000000..a3a7c972960 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.cc @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/time_input_type.h" + +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/core/inspector/console_message.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/date_math.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +using namespace HTMLNames; + +static const int kTimeDefaultStep = 60; +static const int kTimeDefaultStepBase = 0; +static const int kTimeStepScaleFactor = 1000; + +TimeInputType::TimeInputType(HTMLInputElement& element) + : BaseTemporalInputType(element) {} + +InputType* TimeInputType::Create(HTMLInputElement& element) { + return new TimeInputType(element); +} + +void TimeInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeTime); +} + +const AtomicString& TimeInputType::FormControlType() const { + return InputTypeNames::time; +} + +Decimal TimeInputType::DefaultValueForStepUp() const { + DateComponents date; + date.SetMillisecondsSinceMidnight(ConvertToLocalTime(CurrentTimeMS())); + double milliseconds = date.MillisecondsSinceEpoch(); + DCHECK(std::isfinite(milliseconds)); + return Decimal::FromDouble(milliseconds); +} + +StepRange TimeInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL( + const StepRange::StepDescription, step_description, + (kTimeDefaultStep, kTimeDefaultStepBase, kTimeStepScaleFactor, + StepRange::kScaledStepValueShouldBeInteger)); + + return InputType::CreateStepRange( + any_step_handling, kTimeDefaultStepBase, + Decimal::FromDouble(DateComponents::MinimumTime()), + Decimal::FromDouble(DateComponents::MaximumTime()), step_description); +} + +bool TimeInputType::ParseToDateComponentsInternal(const String& string, + DateComponents* out) const { + DCHECK(out); + unsigned end; + return out->ParseTime(string, 0, end) && end == string.length(); +} + +bool TimeInputType::SetMillisecondToDateComponents(double value, + DateComponents* date) const { + DCHECK(date); + return date->SetMillisecondsSinceMidnight(value); +} + +void TimeInputType::WarnIfValueIsInvalid(const String& value) const { + if (value != GetElement().SanitizeValue(value)) { + AddWarningToConsole( + "The specified value %s does not conform to the required format. The " + "format is \"HH:mm\", \"HH:mm:ss\" or \"HH:mm:ss.SSS\" where HH is " + "00-23, mm is 00-59, ss is 00-59, and SSS is 000-999.", + value); + } +} + +String TimeInputType::LocalizeValue(const String& proposed_value) const { + DateComponents date; + if (!ParseToDateComponents(proposed_value, &date)) + return proposed_value; + + Locale::FormatType format_type = ShouldHaveSecondField(date) + ? Locale::kFormatTypeMedium + : Locale::kFormatTypeShort; + + String localized = GetElement().GetLocale().FormatDateTime(date, format_type); + return localized.IsEmpty() ? proposed_value : localized; +} + +String TimeInputType::FormatDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) const { + if (!date_time_fields_state.HasHour() || + !date_time_fields_state.HasMinute() || !date_time_fields_state.HasAMPM()) + return g_empty_string; + if (date_time_fields_state.HasMillisecond() && + date_time_fields_state.Millisecond()) { + return String::Format( + "%02u:%02u:%02u.%03u", date_time_fields_state.Hour23(), + date_time_fields_state.Minute(), + date_time_fields_state.HasSecond() ? date_time_fields_state.Second() + : 0, + date_time_fields_state.Millisecond()); + } + if (date_time_fields_state.HasSecond() && date_time_fields_state.Second()) { + return String::Format("%02u:%02u:%02u", date_time_fields_state.Hour23(), + date_time_fields_state.Minute(), + date_time_fields_state.Second()); + } + return String::Format("%02u:%02u", date_time_fields_state.Hour23(), + date_time_fields_state.Minute()); +} + +void TimeInputType::SetupLayoutParameters( + DateTimeEditElement::LayoutParameters& layout_parameters, + const DateComponents& date) const { + if (ShouldHaveSecondField(date)) { + layout_parameters.date_time_format = layout_parameters.locale.TimeFormat(); + layout_parameters.fallback_date_time_format = "HH:mm:ss"; + } else { + layout_parameters.date_time_format = + layout_parameters.locale.ShortTimeFormat(); + layout_parameters.fallback_date_time_format = "HH:mm"; + } + if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr), + &layout_parameters.minimum)) + layout_parameters.minimum = DateComponents(); + if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr), + &layout_parameters.maximum)) + layout_parameters.maximum = DateComponents(); +} + +bool TimeInputType::IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const { + return has_hour && has_minute && has_ampm; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h new file mode 100644 index 00000000000..91abeeead30 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/time_input_type.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TIME_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TIME_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" + +namespace blink { + +class TimeInputType final : public BaseTemporalInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + explicit TimeInputType(HTMLInputElement&); + + void CountUsage() override; + const AtomicString& FormControlType() const override; + Decimal DefaultValueForStepUp() const override; + StepRange CreateStepRange(AnyStepHandling) const override; + bool ParseToDateComponentsInternal(const String&, + DateComponents*) const override; + bool SetMillisecondToDateComponents(double, DateComponents*) const override; + void WarnIfValueIsInvalid(const String&) const override; + String LocalizeValue(const String&) const override; + + // BaseTemporalInputType functions + String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override; + void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&, + const DateComponents&) const override; + bool IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TIME_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc new file mode 100644 index 00000000000..2081ed3d154 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.cc @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights + * reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * + * 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 "third_party/blink/renderer/core/html/forms/type_ahead.h" + +#include "third_party/blink/renderer/core/events/keyboard_event.h" +#include "third_party/blink/renderer/platform/wtf/text/character_names.h" + +namespace blink { + +TypeAhead::TypeAhead(TypeAheadDataSource* data_source) + : data_source_(data_source), repeating_char_(0) {} + +constexpr TimeDelta kTypeAheadTimeout = TimeDelta::FromSecondsD(1); + +static String StripLeadingWhiteSpace(const String& string) { + unsigned length = string.length(); + + unsigned i; + for (i = 0; i < length; ++i) { + if (string[i] != kNoBreakSpaceCharacter && !IsSpaceOrNewline(string[i])) + break; + } + + return string.Substring(i, length - i); +} + +int TypeAhead::HandleEvent(KeyboardEvent* event, MatchModeFlags match_mode) { + if (event->PlatformTimeStamp() < last_type_time_) + return -1; + + int option_count = data_source_->OptionCount(); + TimeDelta delta = event->PlatformTimeStamp() - last_type_time_; + last_type_time_ = event->PlatformTimeStamp(); + + UChar c = event->charCode(); + + if (delta > kTypeAheadTimeout) + buffer_.Clear(); + + buffer_.Append(c); + + if (option_count < 1) + return -1; + + int search_start_offset = 1; + String prefix; + if (match_mode & kCycleFirstChar && c == repeating_char_) { + // The user is likely trying to cycle through all the items starting + // with this character, so just search on the character. + prefix = String(&c, 1); + repeating_char_ = c; + } else if (match_mode & kMatchPrefix) { + prefix = buffer_.ToString(); + if (buffer_.length() > 1) { + repeating_char_ = 0; + search_start_offset = 0; + } else { + repeating_char_ = c; + } + } + + if (!prefix.IsEmpty()) { + int selected = data_source_->IndexOfSelectedOption(); + int index = (selected < 0 ? 0 : selected) + search_start_offset; + index %= option_count; + + // Compute a case-folded copy of the prefix string before beginning the + // search for a matching element. This code uses foldCase to work around the + // fact that String::startWith does not fold non-ASCII characters. This code + // can be changed to use startWith once that is fixed. + String prefix_with_case_folded(prefix.FoldCase()); + for (int i = 0; i < option_count; ++i, index = (index + 1) % option_count) { + // Fold the option string and check if its prefix is equal to the folded + // prefix. + String text = data_source_->OptionAtIndex(index); + if (StripLeadingWhiteSpace(text).FoldCase().StartsWith( + prefix_with_case_folded)) + return index; + } + } + + if (match_mode & kMatchIndex) { + bool ok = false; + int index = buffer_.ToString().ToInt(&ok); + if (index > 0 && index <= option_count) + return index - 1; + } + return -1; +} + +bool TypeAhead::HasActiveSession(KeyboardEvent* event) { + TimeDelta delta = event->PlatformTimeStamp() - last_type_time_; + return delta <= kTypeAheadTimeout; +} + +void TypeAhead::ResetSession() { + last_type_time_ = TimeTicks(); + buffer_.Clear(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/type_ahead.h b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.h new file mode 100644 index 00000000000..b8b59424884 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/type_ahead.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TYPE_AHEAD_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TYPE_AHEAD_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class KeyboardEvent; + +class CORE_EXPORT TypeAheadDataSource { + public: + virtual ~TypeAheadDataSource() = default; + + virtual int IndexOfSelectedOption() const = 0; + virtual int OptionCount() const = 0; + virtual String OptionAtIndex(int index) const = 0; +}; + +class TypeAhead { + DISALLOW_NEW(); + + public: + TypeAhead(TypeAheadDataSource*); + + enum ModeFlag { + kMatchPrefix = 1 << 0, + kCycleFirstChar = 1 << 1, + kMatchIndex = 1 << 2, + }; + using MatchModeFlags = unsigned; + + // Returns the index for the matching option. + int HandleEvent(KeyboardEvent*, MatchModeFlags); + bool HasActiveSession(KeyboardEvent*); + void ResetSession(); + + private: + TypeAheadDataSource* data_source_; + // platform timestamp of last keyboard event in seconds + TimeTicks last_type_time_; + UChar repeating_char_; + StringBuilder buffer_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_TYPE_AHEAD_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc new file mode 100644 index 00000000000..6cc7a579274 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.cc @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/url_input_type.h" + +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" + +namespace blink { + +InputType* URLInputType::Create(HTMLInputElement& element) { + return new URLInputType(element); +} + +void URLInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeURL); +} + +const AtomicString& URLInputType::FormControlType() const { + return InputTypeNames::url; +} + +bool URLInputType::TypeMismatchFor(const String& value) const { + return !value.IsEmpty() && !KURL(NullURL(), value).IsValid(); +} + +bool URLInputType::TypeMismatch() const { + return TypeMismatchFor(GetElement().value()); +} + +String URLInputType::TypeMismatchText() const { + return GetLocale().QueryString( + WebLocalizedString::kValidationTypeMismatchForURL); +} + +String URLInputType::SanitizeValue(const String& proposed_value) const { + return BaseTextInputType::SanitizeValue( + StripLeadingAndTrailingHTMLSpaces(proposed_value)); +} + +String URLInputType::SanitizeUserInputValue( + const String& proposed_value) const { + // Do not call URLInputType::sanitizeValue. + return BaseTextInputType::SanitizeValue(proposed_value); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/url_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.h new file mode 100644 index 00000000000..6084b52eddc --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/url_input_type.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_URL_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_URL_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_text_input_type.h" + +namespace blink { + +class URLInputType final : public BaseTextInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + URLInputType(HTMLInputElement& element) : BaseTextInputType(element) {} + void CountUsage() override; + const AtomicString& FormControlType() const override; + bool TypeMismatchFor(const String&) const override; + bool TypeMismatch() const override; + String TypeMismatchText() const override; + String SanitizeValue(const String&) const override; + String SanitizeUserInputValue(const String&) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_URL_INPUT_TYPE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/validity_state.cc b/chromium/third_party/blink/renderer/core/html/forms/validity_state.cc new file mode 100644 index 00000000000..d5b64098f78 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/validity_state.cc @@ -0,0 +1,76 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com> + * Copyright (C) 2010 Apple Inc. 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 "third_party/blink/renderer/core/html/forms/validity_state.h" + +namespace blink { + +String ValidityState::ValidationMessage() const { + return control_->validationMessage(); +} + +bool ValidityState::valueMissing() const { + return control_->ValueMissing(); +} + +bool ValidityState::typeMismatch() const { + return control_->TypeMismatch(); +} + +bool ValidityState::patternMismatch() const { + return control_->PatternMismatch(); +} + +bool ValidityState::tooLong() const { + return control_->TooLong(); +} + +bool ValidityState::tooShort() const { + return control_->TooShort(); +} + +bool ValidityState::rangeUnderflow() const { + return control_->RangeUnderflow(); +} + +bool ValidityState::rangeOverflow() const { + return control_->RangeOverflow(); +} + +bool ValidityState::stepMismatch() const { + return control_->StepMismatch(); +} + +bool ValidityState::badInput() const { + return control_->HasBadInput(); +} + +bool ValidityState::customError() const { + return control_->CustomError(); +} + +bool ValidityState::valid() const { + return control_->Valid(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/validity_state.h b/chromium/third_party/blink/renderer/core/html/forms/validity_state.h new file mode 100644 index 00000000000..483bbcd863d --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/validity_state.h @@ -0,0 +1,71 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com> + * Copyright (C) 2010 Apple Inc. 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. + * + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_VALIDITY_STATE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_VALIDITY_STATE_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/core/html/forms/listed_element.h" +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" + +namespace blink { + +class ValidityState final : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + static ValidityState* Create(ListedElement* control) { + return new ValidityState(control); + } + void Trace(blink::Visitor* visitor) override { + visitor->Trace(control_); + ScriptWrappable::Trace(visitor); + } + + String ValidationMessage() const; + + void SetCustomErrorMessage(const String&); + + bool valueMissing() const; + bool typeMismatch() const; + bool patternMismatch() const; + bool tooLong() const; + bool tooShort() const; + bool rangeUnderflow() const; + bool rangeOverflow() const; + bool stepMismatch() const; + bool badInput() const; + bool customError() const; + bool valid() const; + + private: + explicit ValidityState(ListedElement* control) : control_(control) {} + + Member<ListedElement> control_; + + DISALLOW_COPY_AND_ASSIGN(ValidityState); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_VALIDITY_STATE_H_ diff --git a/chromium/third_party/blink/renderer/core/html/forms/validity_state.idl b/chromium/third_party/blink/renderer/core/html/forms/validity_state.idl new file mode 100644 index 00000000000..117bb925acb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/validity_state.idl @@ -0,0 +1,37 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2009 Michelangelo De Simone <micdesim@gmail.com> + * + * 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. + * + */ + +// https://html.spec.whatwg.org/#validitystate + +interface ValidityState { + readonly attribute boolean valueMissing; + readonly attribute boolean typeMismatch; + readonly attribute boolean patternMismatch; + readonly attribute boolean tooLong; + readonly attribute boolean tooShort; + readonly attribute boolean rangeUnderflow; + readonly attribute boolean rangeOverflow; + readonly attribute boolean stepMismatch; + readonly attribute boolean badInput; + readonly attribute boolean customError; + readonly attribute boolean valid; +}; diff --git a/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc new file mode 100644 index 00000000000..1dcd8d01db2 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.cc @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/html/forms/week_input_type.h" + +#include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/html/forms/date_time_fields_state.h" +#include "third_party/blink/renderer/core/html/forms/html_input_element.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/input_type_names.h" +#include "third_party/blink/renderer/platform/date_components.h" +#include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +using namespace HTMLNames; + +static const int kWeekDefaultStepBase = + -259200000; // The first day of 1970-W01. +static const int kWeekDefaultStep = 1; +static const int kWeekStepScaleFactor = 604800000; + +InputType* WeekInputType::Create(HTMLInputElement& element) { + return new WeekInputType(element); +} + +void WeekInputType::CountUsage() { + CountUsageIfVisible(WebFeature::kInputTypeWeek); +} + +const AtomicString& WeekInputType::FormControlType() const { + return InputTypeNames::week; +} + +StepRange WeekInputType::CreateStepRange( + AnyStepHandling any_step_handling) const { + DEFINE_STATIC_LOCAL( + const StepRange::StepDescription, step_description, + (kWeekDefaultStep, kWeekDefaultStepBase, kWeekStepScaleFactor, + StepRange::kParsedStepValueShouldBeInteger)); + + return InputType::CreateStepRange( + any_step_handling, kWeekDefaultStepBase, + Decimal::FromDouble(DateComponents::MinimumWeek()), + Decimal::FromDouble(DateComponents::MaximumWeek()), step_description); +} + +bool WeekInputType::ParseToDateComponentsInternal(const String& string, + DateComponents* out) const { + DCHECK(out); + unsigned end; + return out->ParseWeek(string, 0, end) && end == string.length(); +} + +bool WeekInputType::SetMillisecondToDateComponents(double value, + DateComponents* date) const { + DCHECK(date); + return date->SetMillisecondsSinceEpochForWeek(value); +} + +void WeekInputType::WarnIfValueIsInvalid(const String& value) const { + if (value != GetElement().SanitizeValue(value)) + AddWarningToConsole( + "The specified value %s does not conform to the required format. The " + "format is \"yyyy-Www\" where yyyy is year in four or more digits, and " + "ww is 01-53.", + value); +} + +String WeekInputType::FormatDateTimeFieldsState( + const DateTimeFieldsState& date_time_fields_state) const { + if (!date_time_fields_state.HasYear() || + !date_time_fields_state.HasWeekOfYear()) + return g_empty_string; + return String::Format("%04u-W%02u", date_time_fields_state.Year(), + date_time_fields_state.WeekOfYear()); +} + +void WeekInputType::SetupLayoutParameters( + DateTimeEditElement::LayoutParameters& layout_parameters, + const DateComponents&) const { + layout_parameters.date_time_format = GetLocale().WeekFormatInLDML(); + layout_parameters.fallback_date_time_format = "yyyy-'W'ww"; + if (!ParseToDateComponents(GetElement().FastGetAttribute(minAttr), + &layout_parameters.minimum)) + layout_parameters.minimum = DateComponents(); + if (!ParseToDateComponents(GetElement().FastGetAttribute(maxAttr), + &layout_parameters.maximum)) + layout_parameters.maximum = DateComponents(); + layout_parameters.placeholder_for_year = "----"; +} + +bool WeekInputType::IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const { + return has_year && has_week; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h new file mode 100644 index 00000000000..6486a627dc3 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/html/forms/week_input_type.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_WEEK_INPUT_TYPE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_WEEK_INPUT_TYPE_H_ + +#include "third_party/blink/renderer/core/html/forms/base_temporal_input_type.h" + +namespace blink { + +class WeekInputType final : public BaseTemporalInputType { + public: + static InputType* Create(HTMLInputElement&); + + private: + explicit WeekInputType(HTMLInputElement& element) + : BaseTemporalInputType(element) {} + + void CountUsage() override; + const AtomicString& FormControlType() const override; + StepRange CreateStepRange(AnyStepHandling) const override; + bool ParseToDateComponentsInternal(const String&, + DateComponents*) const override; + bool SetMillisecondToDateComponents(double, DateComponents*) const override; + void WarnIfValueIsInvalid(const String&) const override; + + // BaseTemporalInputType functions + String FormatDateTimeFieldsState(const DateTimeFieldsState&) const override; + void SetupLayoutParameters(DateTimeEditElement::LayoutParameters&, + const DateComponents&) const override; + bool IsValidFormat(bool has_year, + bool has_month, + bool has_week, + bool has_day, + bool has_ampm, + bool has_hour, + bool has_minute, + bool has_second) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_WEEK_INPUT_TYPE_H_ |