| /* |
| * 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/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/frame/use_counter_helper.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/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/fonts/font_cache.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.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 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; |
| |
| 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(); |
| |
| // Mute UseCounter when constructing the DateTime object, to avoid counting |
| // attributes on elements inside the user-agent shadow DOM. |
| UseCounterMuteScope scope(EditElement()); |
| 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 = |
| MakeGarbageCollected<DateTimeDayFieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeHour11FieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeHour12FieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeHour23FieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeHour24FieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeMinuteFieldElement>( |
| 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 = MakeGarbageCollected<DateTimeSymbolicMonthFieldElement>( |
| document, EditElement(), |
| field_type == DateTimeFormat::kFieldTypeMonth |
| ? parameters_.locale.ShortMonthLabels() |
| : parameters_.locale.ShortStandAloneMonthLabels(), |
| min_month, max_month); |
| break; |
| case kCountForFullMonth: |
| field = MakeGarbageCollected<DateTimeSymbolicMonthFieldElement>( |
| document, EditElement(), |
| field_type == DateTimeFormat::kFieldTypeMonth |
| ? parameters_.locale.MonthLabels() |
| : parameters_.locale.StandAloneMonthLabels(), |
| min_month, max_month); |
| break; |
| default: |
| field = MakeGarbageCollected<DateTimeMonthFieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeAMPMFieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeSecondFieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeMillisecondFieldElement>( |
| 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(MakeGarbageCollected<DateTimeWeekFieldElement>( |
| 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 = |
| MakeGarbageCollected<DateTimeYearFieldElement>( |
| 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); |
| auto* element = |
| MakeGarbageCollected<HTMLDivElement>(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(); |
| SetShadowPseudoId(AtomicString("-webkit-datetime-edit")); |
| setAttribute(html_names::kIdAttr, shadow_element_names::kIdDateTimeEdit); |
| } |
| |
| DateTimeEditElement::~DateTimeEditElement() = default; |
| |
| void DateTimeEditElement::Trace(Visitor* visitor) const { |
| visitor->Trace(fields_); |
| visitor->Trace(edit_control_owner_); |
| HTMLDivElement::Trace(visitor); |
| } |
| |
| inline Element* DateTimeEditElement::FieldsWrapperElement() const { |
| CHECK(!firstChild() || IsA<Element>(firstChild())); |
| return To<Element>(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(); |
| } |
| |
| scoped_refptr<ComputedStyle> DateTimeEditElement::CustomStyleForLayoutObject( |
| const StyleRecalcContext& style_recalc_context) { |
| // TODO(crbug.com/1181868): This is a kind of layout. We might want to |
| // introduce new LayoutObject. |
| scoped_refptr<ComputedStyle> style = |
| OriginalStyleForLayoutObject(style_recalc_context); |
| float width = 0; |
| for (Node* child = FieldsWrapperElement()->firstChild(); child; |
| child = child->nextSibling()) { |
| auto* child_element = DynamicTo<Element>(child); |
| if (!child_element) |
| continue; |
| 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::Fixed(ceilf(width))); |
| style->SetCustomStyleCallbackDependsOnFont(); |
| return style; |
| } |
| |
| void DateTimeEditElement::DidBlurFromField(mojom::blink::FocusType focus_type) { |
| if (edit_control_owner_) |
| edit_control_owner_->DidBlurFromControl(focus_type); |
| } |
| |
| void DateTimeEditElement::DidFocusOnField(mojom::blink::FocusType focus_type) { |
| if (edit_control_owner_) |
| edit_control_owner_->DidFocusOnControl(focus_type); |
| } |
| |
| void DateTimeEditElement::DisabledStateChanged() { |
| UpdateUIState(); |
| } |
| |
| DateTimeFieldElement* DateTimeEditElement::FieldAt( |
| wtf_size_t field_index) const { |
| return field_index < fields_.size() ? fields_[field_index].Get() : nullptr; |
| } |
| |
| wtf_size_t DateTimeEditElement::FieldIndexOf( |
| const DateTimeFieldElement& field) const { |
| for (wtf_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); |
| wtf_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()); |
| } |
| |
| wtf_size_t DateTimeEditElement::FocusedFieldIndex() const { |
| Element* const focused_field_element = GetDocument().FocusedElement(); |
| for (wtf_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(wtf_size_t start_index) { |
| GetDocument().UpdateStyleAndLayoutTree(); |
| for (wtf_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 wtf_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 wtf_size_t start_field_index = FieldIndexOf(field); |
| if (start_field_index == kInvalidFieldIndex) |
| return false; |
| GetDocument().UpdateStyleAndLayoutTree(); |
| wtf_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; |
| } |
| |
| void DateTimeEditElement::HandleAmPmRollover( |
| DateTimeFieldElement::FieldRolloverType rollover_type) { |
| auto* ampm_field = GetField(DateTimeField::kAMPM); |
| if (!ampm_field) |
| return; |
| DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState(); |
| ampm_field->PopulateDateTimeFieldsState(date_time_fields_state); |
| bool was_am = |
| (date_time_fields_state.Ampm() == DateTimeFieldsState::kAMPMValueAM); |
| date_time_fields_state.SetAMPM(DateTimeFieldsState::kAMPMValuePM); |
| if (rollover_type != DateTimeFieldElement::FieldRolloverType::kToPm && |
| !was_am) |
| date_time_fields_state.SetAMPM(DateTimeFieldsState::kAMPMValueAM); |
| ampm_field->SetValueAsDateTimeFieldsState(date_time_fields_state); |
| // No need to call EditControlValueChanged since change that triggered |
| // rollover will do so. |
| } |
| |
| 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()) { |
| auto* element = MakeGarbageCollected<HTMLDivElement>(GetDocument()); |
| element->SetShadowPseudoId(fields_wrapper_pseudo_id); |
| AppendChild(element); |
| } |
| Element* fields_wrapper = FieldsWrapperElement(); |
| |
| wtf_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 (wtf_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(style_change_reason::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); |
| } |
| |
| DateTimeFieldElement* DateTimeEditElement::GetField(DateTimeField type) const { |
| auto* it = std::find_if( |
| fields_.begin(), fields_.end(), |
| [&type](const auto& field) { return field->Type() == type; }); |
| if (it == fields_.end()) |
| return nullptr; |
| return *it; |
| } |
| |
| bool DateTimeEditElement::HasField(DateTimeField type) const { |
| for (const auto& field : fields_) { |
| if (field->Type() == type) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool DateTimeEditElement::IsFirstFieldAMPM() const { |
| const auto* first_field = FieldAt(0); |
| return first_field && first_field->Type() == DateTimeField::kAMPM; |
| } |
| |
| bool DateTimeEditElement::HasFocusedField() { |
| return FocusedFieldIndex() != kInvalidFieldIndex; |
| } |
| |
| void PopulateOnlyYearMonthDay(const DateComponents& date, |
| DateTimeFieldsState& date_time_fields_state) { |
| date_time_fields_state.SetYear(date.FullYear()); |
| date_time_fields_state.SetMonth(date.Month() + 1); |
| date_time_fields_state.SetDayOfMonth(date.MonthDay()); |
| } |
| |
| void DateTimeEditElement::SetOnlyYearMonthDay(const DateComponents& date) { |
| DCHECK_EQ(date.GetType(), DateComponents::kDate); |
| |
| if (!edit_control_owner_) |
| return; |
| |
| DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState(); |
| PopulateOnlyYearMonthDay(date, date_time_fields_state); |
| SetValueAsDateTimeFieldsState(date_time_fields_state); |
| edit_control_owner_->EditControlValueChanged(); |
| } |
| |
| void PopulateOnlyTime(const DateComponents& date, |
| DateTimeFieldsState& date_time_fields_state) { |
| date_time_fields_state.SetHour(date.Hour() % 12 ? date.Hour() % 12 : 12); |
| date_time_fields_state.SetMinute(date.Minute()); |
| date_time_fields_state.SetSecond(date.Second()); |
| date_time_fields_state.SetMillisecond(date.Millisecond()); |
| date_time_fields_state.SetAMPM(date.Hour() >= 12 |
| ? DateTimeFieldsState::kAMPMValuePM |
| : DateTimeFieldsState::kAMPMValueAM); |
| } |
| |
| void DateTimeEditElement::SetOnlyTime(const DateComponents& date) { |
| DCHECK_EQ(date.GetType(), DateComponents::kTime); |
| |
| if (!edit_control_owner_) |
| return; |
| |
| DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState(); |
| PopulateOnlyTime(date, date_time_fields_state); |
| SetValueAsDateTimeFieldsState(date_time_fields_state); |
| edit_control_owner_->EditControlValueChanged(); |
| } |
| |
| void PopulateDateTimeLocal(const DateComponents& date, |
| DateTimeFieldsState& date_time_fields_state) { |
| PopulateOnlyYearMonthDay(date, date_time_fields_state); |
| PopulateOnlyTime(date, date_time_fields_state); |
| } |
| |
| void DateTimeEditElement::SetDateTimeLocal(const DateComponents& date) { |
| DCHECK_EQ(date.GetType(), DateComponents::kDateTimeLocal); |
| |
| if (!edit_control_owner_) |
| return; |
| |
| DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState(); |
| PopulateDateTimeLocal(date, date_time_fields_state); |
| 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 |