[macOS] Add editability to input type=time
https://bugs.webkit.org/show_bug.cgi?id=216188

Reviewed by Devin Rousso.

Source/WebCore:

This patch adds editability to input type=time by leveraging existing
logic to add editable components to date/time inputs.

DateTime{Hour|Minute|Second|Millisecond|Meridiem}FieldElements were
created to represent the new editable fields. By default, only the
hour and minute fields are displayed. However, the millisecond
and second fields may be added depending on the initial value of
the element, or the value of the step attribute.

Tests: fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html
       fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html
       fast/forms/time/time-editable-components/time-editable-components-mouse-events.html
       fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html

* css/html.css:

Update stylesheet to handle hour, minute, second, millisecond and meridiem fields.

(input::-webkit-datetime-edit-fields-wrapper):
(input::-webkit-datetime-edit-year-field,):
(input::-webkit-datetime-edit-year-field:focus,):
(input[disabled]::-webkit-datetime-edit-year-field,):
* html/BaseChooserOnlyDateAndTimeInputType.cpp:
(WebCore::DateTimeFormatValidator::visitField):
(WebCore::BaseChooserOnlyDateAndTimeInputType::updateInnerTextValue):
(WebCore::BaseChooserOnlyDateAndTimeInputType::attributeChanged):

The step attribute can determine whether the second and/or millisecond
fields are displayed. Consequently, we should update the fields in
m_dateTimeEditElement when the step attribute is changed.

* html/BaseChooserOnlyDateAndTimeInputType.h:

setupLayoutParameters() now takes an additional DateComponents argument.
This argument is needed to determine whether the second and/or
millisecond field is displayed.

* html/BaseDateAndTimeInputType.h:
* html/DateInputType.cpp:
(WebCore::DateInputType::setupLayoutParameters const):
* html/DateInputType.h:
* html/DateTimeFieldsState.h:
* html/DateTimeLocalInputType.cpp:
(WebCore::DateTimeLocalInputType::setupLayoutParameters const):
* html/DateTimeLocalInputType.h:
* html/MonthInputType.cpp:
(WebCore::MonthInputType::setupLayoutParameters const):
* html/MonthInputType.h:
* html/TimeInputType.cpp:
(WebCore::TimeInputType::isValidFormat const):
(WebCore::TimeInputType::formatDateTimeFieldsState const):
(WebCore::TimeInputType::setupLayoutParameters const):

The millisecond field is displayed if the date has a non-zero value for
milliseconds, or if the step attribute has sub-second precision. The
second field is displayed if the millisecond field is displayed, if the
date has a non-zero value for seconds, or if the step attribute has
sub-minute precision.

* html/TimeInputType.h:
* html/WeekInputType.cpp:
(WebCore::WeekInputType::setupLayoutParameters const):
* html/WeekInputType.h:
* html/shadow/DateTimeEditElement.cpp:
(WebCore::DateTimeEditBuilder::visitField): Updated to add new field types to the element.
* html/shadow/DateTimeEditElement.h:
* html/shadow/DateTimeFieldElements.cpp:
(WebCore::DateTimeHourFieldElement::DateTimeHourFieldElement):
(WebCore::DateTimeHourFieldElement::create):
(WebCore::DateTimeHourFieldElement::populateDateTimeFieldsState):
(WebCore::DateTimeHourFieldElement::setValueAsDate):
(WebCore::DateTimeMeridiemFieldElement::DateTimeMeridiemFieldElement):
(WebCore::DateTimeMeridiemFieldElement::create):
(WebCore::DateTimeMeridiemFieldElement::populateDateTimeFieldsState):
(WebCore::DateTimeMeridiemFieldElement::setValueAsDate):
(WebCore::DateTimeMillisecondFieldElement::DateTimeMillisecondFieldElement):
(WebCore::DateTimeMillisecondFieldElement::create):
(WebCore::DateTimeMillisecondFieldElement::populateDateTimeFieldsState):
(WebCore::DateTimeMillisecondFieldElement::setValueAsDate):
(WebCore::DateTimeMinuteFieldElement::DateTimeMinuteFieldElement):
(WebCore::DateTimeMinuteFieldElement::create):
(WebCore::DateTimeMinuteFieldElement::populateDateTimeFieldsState):
(WebCore::DateTimeMinuteFieldElement::setValueAsDate):
(WebCore::DateTimeSecondFieldElement::DateTimeSecondFieldElement):
(WebCore::DateTimeSecondFieldElement::create):
(WebCore::DateTimeSecondFieldElement::populateDateTimeFieldsState):
(WebCore::DateTimeSecondFieldElement::setValueAsDate):
* html/shadow/DateTimeFieldElements.h:
* html/shadow/DateTimeNumericFieldElement.cpp:
(WebCore::DateTimeNumericFieldElement::maximum const):
* html/shadow/DateTimeNumericFieldElement.h:
* html/shadow/DateTimeSymbolicFieldElement.cpp:
(WebCore::DateTimeSymbolicFieldElement::DateTimeSymbolicFieldElement):
(WebCore::DateTimeSymbolicFieldElement::handleKeyboardEvent): Implement editing using the same typeahead behavior as <select> elements.
(WebCore::DateTimeSymbolicFieldElement::indexOfSelectedOption const):
(WebCore::DateTimeSymbolicFieldElement::optionCount const):
(WebCore::DateTimeSymbolicFieldElement::optionAtIndex const):
* html/shadow/DateTimeSymbolicFieldElement.h:
* platform/text/PlatformLocale.cpp:
(WebCore::Locale::localizedDecimalSeparator):

Added method to ensure the correct decimal separator is displayed
depending on the user's locale. This separator is used when
the millisecond field is present.

* platform/text/PlatformLocale.h:

LayoutTests:

* TestExpectations:
* fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events-expected.txt: Added.
* fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html: Added.
* fast/forms/time/time-editable-components/time-editable-components-keyboard-events-expected.txt: Added.
* fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html: Added.
* fast/forms/time/time-editable-components/time-editable-components-mouse-events-expected.txt: Added.
* fast/forms/time/time-editable-components/time-editable-components-mouse-events.html: Added.
* fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field-expected.txt: Added.
* fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html: Added.
* platform/mac-wk2/TestExpectations:
* platform/mac-wk2/fast/forms/time/time-appearance-basic-expected.txt: Rebaselined for new appearance.
* platform/mac-wk2/fast/forms/time/time-input-rendering-basic-expected.txt: Rebaselined for new appearance.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@266779 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index b818d14..c9318c2 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,23 @@
+2020-09-09  Aditya Keerthi  <akeerthi@apple.com>
+
+        [macOS] Add editability to input type=time
+        https://bugs.webkit.org/show_bug.cgi?id=216188
+
+        Reviewed by Devin Rousso.
+
+        * TestExpectations:
+        * fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events-expected.txt: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-keyboard-events-expected.txt: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-mouse-events-expected.txt: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-mouse-events.html: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field-expected.txt: Added.
+        * fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html: Added.
+        * platform/mac-wk2/TestExpectations:
+        * platform/mac-wk2/fast/forms/time/time-appearance-basic-expected.txt: Rebaselined for new appearance.
+        * platform/mac-wk2/fast/forms/time/time-input-rendering-basic-expected.txt: Rebaselined for new appearance.
+
 2020-09-08  Ryan Haddad  <ryanhaddad@apple.com>
 
         Unreviewed test gardening after r266761.
diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations
index 5d28ba6..15a920f 100644
--- a/LayoutTests/TestExpectations
+++ b/LayoutTests/TestExpectations
@@ -31,6 +31,7 @@
 fast/css/ios [ Skip ]
 fast/css/watchos [ Skip ]
 fast/forms/date/date-editable-components [ Skip ]
+fast/forms/time/time-editable-components [ Skip ]
 fast/dom/Window/watchos [ Skip ]
 fast/forms/select/mac-wk2 [ Skip ]
 fast/forms/textarea/ios [ Skip ]
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events-expected.txt b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events-expected.txt
new file mode 100644
index 0000000..f020c6b
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events-expected.txt
@@ -0,0 +1,61 @@
+Test for focus and blur events for <input type=time>
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Focus/blur using mouse
+
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 1
+
+Focus/blur using keyboard
+
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS focusEventsFired is 1
+PASS blurEventsFired is 1
+PASS focusEventsFired is 2
+PASS blurEventsFired is 1
+PASS focusEventsFired is 2
+PASS blurEventsFired is 1
+PASS focusEventsFired is 2
+PASS blurEventsFired is 1
+PASS focusEventsFired is 2
+PASS blurEventsFired is 2
+
+Focus/blur on disabled input
+
+PASS focusEventsFired is 0
+PASS blurEventsFired is 0
+PASS document.activeElement.id is "after"
+PASS focusEventsFired is 0
+PASS blurEventsFired is 0
+PASS document.activeElement.id is "before"
+PASS focusEventsFired is 0
+PASS blurEventsFired is 0
+
+Focus/blur on readonly input
+
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS document.activeElement.id is "after"
+PASS focusEventsFired is 1
+PASS blurEventsFired is 1
+PASS focusEventsFired is 1
+PASS blurEventsFired is 1
+PASS successfullyParsed is true
+
+TEST COMPLETE
+  
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html
new file mode 100644
index 0000000..4122c56
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/js-test-pre.js"></script>
+<script src="../../../../resources/ui-helper.js"></script>
+<style>
+input {
+    width: 300px;
+}
+
+input::-webkit-datetime-edit-text {
+    font-size: 30px;
+}
+
+input::-webkit-datetime-edit-hour-field {
+    font-size: 30px;
+}
+
+input::-webkit-datetime-edit-minute-field {
+    font-size: 30px;
+}
+
+input::-webkit-datetime-edit-meridiem-field {
+    font-size: 30px;
+}
+</style>
+</head>
+<body>
+
+<input id="before" type="text">
+<input id="input" type="time" value="18:30">
+<input id="after" type="text">
+
+<script>
+
+description("Test for focus and blur events for &lt;input type=time&gt;");
+
+blurEventsFired = 0;
+function onBlurEvent() {
+    blurEventsFired++;
+}
+
+focusEventsFired = 0;
+function onFocusEvent() {
+    focusEventsFired++;
+}
+
+function assertFocusAndBlurCount(numFocusEvents, numBlurEvents) {
+    shouldBe("focusEventsFired", numFocusEvents.toString());
+    shouldBe("blurEventsFired", numBlurEvents.toString());
+}
+
+function resetFocusAndBlurCount() {
+    blurEventsFired = 0;
+    focusEventsFired = 0;
+}
+
+function mouseClickOn(x, y) {
+    if (!window.eventSender)
+        return;
+    eventSender.mouseMoveTo(x + input.offsetLeft, y + input.offsetTop);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+
+input.addEventListener("blur", onBlurEvent);
+input.addEventListener("focus", onFocusEvent);
+
+const center = input.offsetHeight / 2;
+
+debug("Focus/blur using mouse\n");
+
+// Click on hour field.
+mouseClickOn(20, center);
+assertFocusAndBlurCount(1, 0);
+// Click on minute field.
+mouseClickOn(60, center);
+assertFocusAndBlurCount(1, 0);
+// Click on AM/PM field.
+mouseClickOn(120, center);
+assertFocusAndBlurCount(1, 0);
+// Click on control, but not a specific field.
+mouseClickOn(250, center);
+assertFocusAndBlurCount(1, 0);
+// Click outside control.
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+assertFocusAndBlurCount(1, 1);
+resetFocusAndBlurCount();
+
+debug("\nFocus/blur using keyboard\n");
+
+UIHelper.activateElement(before);
+// Focus on hour field.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(1, 0);
+// Focus on minute field.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(1, 0);
+// Focus on AM/PM field.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(1, 0);
+// Focus out.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(1, 1);
+// Focus on hour field.
+UIHelper.keyDown("\t", ["shiftKey"]);
+assertFocusAndBlurCount(2, 1);
+// Focus on minute field.
+UIHelper.keyDown("\t", ["shiftKey"]);
+assertFocusAndBlurCount(2, 1);
+// Focus on AM/PM field.
+UIHelper.keyDown("\t", ["shiftKey"]);
+assertFocusAndBlurCount(2, 1);
+// Focus out.
+UIHelper.keyDown("\t", ["shiftKey"]);
+assertFocusAndBlurCount(2, 2);
+resetFocusAndBlurCount();
+
+debug("\nFocus/blur on disabled input\n")
+
+input.disabled = true;
+
+UIHelper.activateElement(before);
+// Tab to focus should skip disabled input.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(0, 0);
+shouldBeEqualToString("document.activeElement.id", "after");
+// Shift+Tab should skip disabled input.
+UIHelper.keyDown("\t", ["shiftKey"]);
+assertFocusAndBlurCount(0, 0);
+shouldBeEqualToString("document.activeElement.id", "before");
+// Clicking on any part of the control should not focus/blur events.
+mouseClickOn(20, center);
+mouseClickOn(60, center);
+mouseClickOn(120, center);
+mouseClickOn(250, center);
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+assertFocusAndBlurCount(0, 0);
+resetFocusAndBlurCount();
+
+debug("\nFocus/blur on readonly input\n")
+
+input.disabled = false;
+input.readOnly = true;
+
+UIHelper.activateElement(before);
+// Tab to focus should not skip readonly input.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(1, 0);
+UIHelper.keyDown("\t");
+UIHelper.keyDown("\t");
+UIHelper.keyDown("\t");
+shouldBeEqualToString("document.activeElement.id", "after");
+assertFocusAndBlurCount(1, 1);
+// Clicking on any part of the control should fire the appropriate events.
+mouseClickOn(20, center);
+mouseClickOn(60, center);
+mouseClickOn(120, center);
+mouseClickOn(250, center);
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+assertFocusAndBlurCount(1, 1);
+resetFocusAndBlurCount();
+
+</script>
+
+<script src="../../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-keyboard-events-expected.txt b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-keyboard-events-expected.txt
new file mode 100644
index 0000000..08b49d3
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-keyboard-events-expected.txt
@@ -0,0 +1,72 @@
+Test for keyboard operations for <input type=time>
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Digit keys
+PASS input.value is "00:02"
+PASS changeEventsFired is 1
+PASS inputEventsFired is 1
+
+Digit keys with timeout
+PASS input.value is "14:05"
+PASS changeEventsFired is 1
+PASS inputEventsFired is 1
+
+Digit keys clamp value
+PASS input.value is "13:22"
+PASS input.value is "12:22"
+PASS input.value is "13:22"
+PASS input.value is "13:06"
+PASS input.value is "13:59"
+PASS changeEventsFired is 5
+PASS inputEventsFired is 5
+
+Left/Right arrow keys
+PASS input.value is "02:02"
+PASS input.value is "03:03"
+PASS changeEventsFired is 5
+PASS inputEventsFired is 5
+
+Up/Down arrow keys
+PASS input.value is "12:59"
+PASS input.value is "13:59"
+PASS input.value is "12:59"
+PASS input.value is "23:59"
+PASS input.value is "11:59"
+PASS input.value is "23:59"
+PASS input.value is "11:59"
+PASS changeEventsFired is 7
+PASS inputEventsFired is 7
+
+Tab key
+PASS input.value is "02:02"
+PASS document.activeElement.id is "after"
+PASS input.value is "03:03"
+PASS document.activeElement.id is "before"
+PASS changeEventsFired is 3
+PASS inputEventsFired is 3
+
+Backspace key
+PASS input.value is ""
+PASS input.value is "19:30"
+PASS changeEventsFired is 2
+PASS inputEventsFired is 2
+
+Delete key
+PASS input.value is ""
+PASS changeEventsFired is 1
+PASS inputEventsFired is 1
+
+Disabled/readonly
+PASS input.value is "09:01"
+PASS input.value is "01:01"
+PASS input.value is "01:01"
+PASS input.value is "01:02"
+PASS changeEventsFired is 2
+PASS inputEventsFired is 2
+PASS successfullyParsed is true
+
+TEST COMPLETE
+  
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html
new file mode 100644
index 0000000..1babd95
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/js-test-pre.js"></script>
+<script src="../../../../resources/ui-helper.js"></script>
+</head>
+<body>
+
+<input id="before" type="text">
+<input id="input" type="time">
+<input id="after" type="text">
+
+<script>
+
+jsTestIsAsync = true;
+
+changeEventsFired = 0;
+function onChangeEvent() {
+    changeEventsFired++;
+}
+
+inputEventsFired = 0;
+function onInputEvent() {
+    inputEventsFired++;
+}
+
+function beginTest(title, value) {
+    debug("\n" + title);
+    input.value = value || "";
+    input.blur();
+    input.focus();
+
+    changeEventsFired = 0;
+    inputEventsFired = 0;
+}
+
+input.addEventListener("change", onChangeEvent);
+input.addEventListener("input", onInputEvent);
+
+addEventListener("load", async () => {
+    description("Test for keyboard operations for &lt;input type=time&gt;");
+
+    beginTest("Digit keys");                               // [hh]:mm tt
+    UIHelper.keyDown("1");                                 // -> [01]:mm tt
+    UIHelper.keyDown("2");                                 // -> [12]:mm tt
+    UIHelper.keyDown("rightArrow");                        // -> 12:[mm] tt
+    UIHelper.keyDown("2");                                 // -> 12:[02] tt
+    UIHelper.keyDown("rightArrow");                        // -> 12:02 [tt]
+    UIHelper.keyDown("A");                                 // -> 12:02 [AM]
+    shouldBeEqualToString("input.value", "00:02");
+    shouldBe("changeEventsFired", "1");
+    shouldBe("inputEventsFired", "1");
+
+    beginTest("Digit keys with timeout");                  // [hh]:mm tt
+    UIHelper.keyDown("2");                                 // [02]:mm tt
+    UIHelper.keyDown("rightArrow");                        // -> 02:[mm] tt
+    UIHelper.keyDown("4");                                 // -> 02:[04] tt
+    await UIHelper.delayFor(1500);                         // Wait.
+    UIHelper.keyDown("5");                                 // -> 02:[05] tt
+    UIHelper.keyDown("rightArrow");                        // -> 02:05 [tt]
+    UIHelper.keyDown("P");                                 // -> 02:05 [PM]
+    shouldBeEqualToString("input.value", "14:05");
+    shouldBe("changeEventsFired", "1");
+    shouldBe("inputEventsFired", "1");
+
+    beginTest("Digit keys clamp value", "12:22");          // [12]:22 PM
+    UIHelper.keyDown("1");                                 // -> [01]:22 PM
+    shouldBeEqualToString("input.value", "13:22");
+    UIHelper.keyDown("3");                                 // -> [12]:22 PM
+    shouldBeEqualToString("input.value", "12:22");
+    UIHelper.keyDown("0");                                 // -> [01]:22 PM
+    shouldBeEqualToString("input.value", "13:22");
+    UIHelper.keyDown("rightArrow");                        // -> 13:[22] PM
+    UIHelper.keyDown("6");                                 // -> 13:[06] PM
+    shouldBeEqualToString("input.value", "13:06");
+    UIHelper.keyDown("1");                                 // -> 13:[59] PM
+    shouldBeEqualToString("input.value", "13:59");
+    shouldBe("changeEventsFired", "5");
+    shouldBe("inputEventsFired", "5");
+
+    beginTest("Left/Right arrow keys", "15:27");           // [03]:27 PM
+    UIHelper.keyDown("2");                                 // -> [02]:27 PM
+    UIHelper.keyDown("rightArrow");                        // -> 02:[27] PM
+    UIHelper.keyDown("2");                                 // -> 02:[02] PM
+    UIHelper.keyDown("rightArrow");                        // -> 02:02 [PM]
+    UIHelper.keyDown("A");                                 // -> 02:02 [AM]
+    shouldBeEqualToString("input.value", "02:02");
+    UIHelper.keyDown("leftArrow");                         // -> 02:[02] AM
+    UIHelper.keyDown("3");                                 // -> 02:[03] AM
+    UIHelper.keyDown("leftArrow");                         // -> [02]:03 AM
+    UIHelper.keyDown("3");                                 // -> [03]:03 AM
+    shouldBeEqualToString("input.value", "03:03");
+    shouldBe("changeEventsFired", "5");
+    shouldBe("inputEventsFired", "5");
+
+    beginTest("Up/Down arrow keys", "23:59");              // [11]:59 PM
+    UIHelper.keyDown("upArrow");                           // -> [12]:59 PM
+    shouldBeEqualToString("input.value", "12:59");
+    UIHelper.keyDown("upArrow");                           // -> [01]:59 PM
+    shouldBeEqualToString("input.value", "13:59");
+    UIHelper.keyDown("downArrow");                         // -> [12]:59 PM
+    shouldBeEqualToString("input.value", "12:59");
+    UIHelper.keyDown("downArrow");                         // -> [11]:59 PM
+    shouldBeEqualToString("input.value", "23:59");
+    UIHelper.keyDown("rightArrow");                        // -> 11:[59] PM
+    UIHelper.keyDown("rightArrow");                        // -> 11:59 [PM]
+    UIHelper.keyDown("upArrow");                           // -> 11:59 [AM]
+    shouldBeEqualToString("input.value", "11:59");
+    UIHelper.keyDown("downArrow");                         // -> 11:59 [PM]
+    shouldBeEqualToString("input.value", "23:59");
+    UIHelper.keyDown("downArrow");                         // -> 11:59 [AM]
+    shouldBeEqualToString("input.value", "11:59");
+    shouldBe("changeEventsFired", "7");
+    shouldBe("inputEventsFired", "7");
+
+    beginTest("Tab key");                                  // [hh]:mm tt
+    UIHelper.keyDown("2");                                 // -> [02]:mm tt
+    UIHelper.keyDown("\t");                                // -> 02:[mm] tt
+    UIHelper.keyDown("2");                                 // -> 02:[02] tt
+    UIHelper.keyDown("\t");                                // -> 02:02 [tt]
+    UIHelper.keyDown("A");                                 // -> 02:02 [AM]
+    shouldBeEqualToString("input.value", "02:02");
+    UIHelper.keyDown("\t");                                // Focus out.
+    shouldBeEqualToString("document.activeElement.id", "after");
+    UIHelper.keyDown("\t", ["shiftKey"]);                  // -> 02:02 [AM]
+    UIHelper.keyDown("\t", ["shiftKey"]);                  // -> 02:[02] AM
+    UIHelper.keyDown("3");                                 // -> 02:[03] AM
+    UIHelper.keyDown("\t", ["shiftKey"]);                  // -> [02]:03 AM
+    UIHelper.keyDown("3");                                 // -> [03]:03 AM
+    shouldBeEqualToString("input.value", "03:03");
+    UIHelper.keyDown("\t", ["shiftKey"]);                  // Focus out.
+    shouldBeEqualToString("document.activeElement.id", "before");
+    shouldBe("changeEventsFired", "3");
+    shouldBe("inputEventsFired", "3");
+
+    beginTest("Backspace key", "16:30");                   // [04]:30 PM
+    UIHelper.keyDown("\b");                                // -> [hh]:30 PM
+    shouldBeEqualToString("input.value", "");
+    UIHelper.keyDown("7");                                 // -> [07]:30 PM
+    shouldBeEqualToString("input.value", "19:30");
+    shouldBe("changeEventsFired", "2");
+    shouldBe("inputEventsFired", "2");
+
+    beginTest("Delete key", "18:20");                      // [06]:20 PM
+    UIHelper.keyDown("delete");                            // -> [hh]:20 PM
+    shouldBeEqualToString("input.value", "");
+    shouldBe("changeEventsFired", "1");
+    shouldBe("inputEventsFired", "1");
+
+    beginTest("Disabled/readonly", "09:01");
+    input.disabled = true;
+    UIHelper.keyDown("1");
+    shouldBeEqualToString("input.value", "09:01");
+    input.disabled = false;
+    input.focus();
+    UIHelper.keyDown("1");
+    shouldBeEqualToString("input.value", "01:01");
+    input.readOnly = true;
+    UIHelper.keyDown("rightArrow");
+    UIHelper.keyDown("2");
+    shouldBeEqualToString("input.value", "01:01");
+    input.readOnly = false;
+    UIHelper.keyDown("2");
+    shouldBeEqualToString("input.value", "01:02");
+    shouldBe("changeEventsFired", "2");
+    shouldBe("inputEventsFired", "2");
+
+    finishJSTest();
+});
+
+</script>
+
+<script src="../../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-mouse-events-expected.txt b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-mouse-events-expected.txt
new file mode 100644
index 0000000..5111436
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-mouse-events-expected.txt
@@ -0,0 +1,25 @@
+Test for mouse events for <input type=time>
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Enabled Input
+
+PASS input.value is "09:30"
+PASS input.value is "09:12"
+PASS input.value is "21:12"
+PASS input.value is "18:12"
+PASS input.value is "18:12"
+PASS clickEventsFired is 4
+
+Disabled Input
+
+PASS clickEventsFired is 0
+
+Readonly Input
+
+PASS clickEventsFired is 4
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-mouse-events.html b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-mouse-events.html
new file mode 100644
index 0000000..20f4f6c
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-mouse-events.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/js-test-pre.js"></script>
+<script src="../../../../resources/ui-helper.js"></script>
+<style>
+input {
+    width: 300px;
+}
+
+input::-webkit-datetime-edit-text {
+    font-size: 30px;
+}
+
+input::-webkit-datetime-edit-hour-field {
+    font-size: 30px;
+}
+
+input::-webkit-datetime-edit-minute-field {
+    font-size: 30px;
+}
+
+input::-webkit-datetime-edit-meridiem-field {
+    font-size: 30px;
+}
+</style>
+</head>
+<body>
+
+<input id="input" type="time" value="05:30">
+
+<script>
+
+description("Test for mouse events for &lt;input type=time&gt;");
+
+clickEventsFired = 0;
+function onClickEvent() {
+    clickEventsFired++;
+}
+
+function mouseClickOn(x, y) {
+    if (!window.eventSender)
+        return;
+    eventSender.mouseMoveTo(x + input.offsetLeft, y + input.offsetTop);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+
+input.addEventListener("click", onClickEvent);
+const center = input.offsetHeight / 2;
+
+debug("Enabled Input\n");
+
+// Click on hour field.
+mouseClickOn(20, center);
+UIHelper.keyDown("9");
+shouldBeEqualToString("input.value", "09:30");
+
+// Click on minute field.
+mouseClickOn(60, center);
+UIHelper.keyDown("1");
+UIHelper.keyDown("2");
+shouldBeEqualToString("input.value", "09:12");
+
+// Click on AM/PM field.
+mouseClickOn(120, center);
+UIHelper.keyDown("upArrow");
+shouldBeEqualToString("input.value", "21:12");
+
+// Click on control, but not a specific field, defaults to first field.
+mouseClickOn(250, center);
+UIHelper.keyDown("6");
+shouldBeEqualToString("input.value", "18:12");
+
+// Click outside control.
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+UIHelper.keyDown("5");
+shouldBeEqualToString("input.value", "18:12");
+
+shouldBe("clickEventsFired", "4");
+
+debug("\nDisabled Input\n");
+clickEventsFired = 0;
+input.disabled = true;
+input.readOnly = false;
+
+// Click on hour field.
+mouseClickOn(20, center);
+// Click on minute field.
+mouseClickOn(60, center);
+// Click on AM/PM field.
+mouseClickOn(120, center);
+// Click on control, but not a specific field, defaults to first field.
+mouseClickOn(250, center);
+// Click outside control.
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+
+shouldBe("clickEventsFired", "0");
+
+debug("\nReadonly Input\n");
+clickEventsFired = 0;
+input.disabled = false;
+input.readOnly = true;
+
+// Click on hour field.
+mouseClickOn(20, center);
+// Click on minute field.
+mouseClickOn(60, center);
+// Click on AM/PM field.
+mouseClickOn(120, center);
+// Click on control, but not a specific field, defaults to first field.
+mouseClickOn(250, center);
+// Click outside control.
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+
+shouldBe("clickEventsFired", "4");
+
+</script>
+
+<script src="../../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field-expected.txt b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field-expected.txt
new file mode 100644
index 0000000..648c43f
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field-expected.txt
@@ -0,0 +1,40 @@
+Test for presence of second and millisecond fields in <input type=time>
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Default
+PASS numberOfFields is 3
+
+Zero seconds
+PASS numberOfFields is 3
+
+Zero seconds and zero milliseconds
+PASS numberOfFields is 3
+
+Zero seconds and non-zero milliseconds
+PASS numberOfFields is 5
+
+Non-zero seconds
+PASS numberOfFields is 4
+
+Non-zero seconds and zero milliseconds
+PASS numberOfFields is 4
+
+Non-zero seconds and non-zero milliseconds
+PASS numberOfFields is 5
+
+Step attribute minute precision
+PASS numberOfFields is 3
+
+Step attribute second precision
+PASS numberOfFields is 4
+
+Step attribute millisecond precision
+PASS numberOfFields is 5
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+  
diff --git a/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html
new file mode 100644
index 0000000..8040ac5
--- /dev/null
+++ b/LayoutTests/fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../../resources/js-test-pre.js"></script>
+<script src="../../../../resources/ui-helper.js"></script>
+</head>
+<body>
+
+<input id="before" type="text">
+<input id="input" type="time">
+<input id="after" type="text">
+
+<script>
+
+jsTestIsAsync = true;
+
+function beginTest(title, value, step) {
+    debug("\n" + title);
+    input.value = value || "";
+    input.step = step || "";
+    input.blur();
+    input.focus();
+}
+
+function verifyNumberOfFields(expected) {
+    numberOfFields = 0;
+
+    // Focus first field.
+    input.blur();
+    input.focus();
+
+    while (document.activeElement !== after) {
+        numberOfFields++;
+        UIHelper.keyDown("\t");
+    }
+
+    // Return focus to first field.
+    input.blur();
+    input.focus();
+
+    shouldBe("numberOfFields", expected.toString());
+}
+
+addEventListener("load", async () => {
+    description("Test for presence of second and millisecond fields in &lt;input type=time&gt;");
+
+    defaultNumberOfFields = 3; // Hour, minute, and AM/PM fields
+    numberOfFieldsWithSeconds = defaultNumberOfFields + 1;
+    numberOfFieldsWithMilliseconds = numberOfFieldsWithSeconds + 1;
+
+    beginTest("Default", "10:20");
+    verifyNumberOfFields(defaultNumberOfFields);
+
+    beginTest("Zero seconds", "10:20:00");
+    verifyNumberOfFields(defaultNumberOfFields);
+
+    beginTest("Zero seconds and zero milliseconds", "10:20:00.000");
+    verifyNumberOfFields(defaultNumberOfFields);
+
+    beginTest("Zero seconds and non-zero milliseconds", "10:20:00.567");
+    verifyNumberOfFields(numberOfFieldsWithMilliseconds);
+
+    beginTest("Non-zero seconds", "10:20:45");
+    verifyNumberOfFields(numberOfFieldsWithSeconds);
+
+    beginTest("Non-zero seconds and zero milliseconds", "10:20:45.000");
+    verifyNumberOfFields(numberOfFieldsWithSeconds);
+
+    beginTest("Non-zero seconds and non-zero milliseconds", "10:20:45.567");
+    verifyNumberOfFields(numberOfFieldsWithMilliseconds);
+
+    beginTest("Step attribute minute precision", "10:20", "60");
+    verifyNumberOfFields(defaultNumberOfFields);
+
+    beginTest("Step attribute second precision", "10:20", "2");
+    verifyNumberOfFields(numberOfFieldsWithSeconds);
+
+    beginTest("Step attribute millisecond precision", "10:20", "0.5");
+    verifyNumberOfFields(numberOfFieldsWithMilliseconds);
+
+    debug("");
+    finishJSTest();
+});
+
+</script>
+
+<script src="../../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/platform/mac-wk2/TestExpectations b/LayoutTests/platform/mac-wk2/TestExpectations
index fbd3092..47d01e0 100644
--- a/LayoutTests/platform/mac-wk2/TestExpectations
+++ b/LayoutTests/platform/mac-wk2/TestExpectations
@@ -13,6 +13,7 @@
 editing/pasteboard/dom-paste [ Pass ]
 fast/events/cursors [ Pass ]
 fast/forms/date/date-editable-components [ Pass ]
+fast/forms/time/time-editable-components [ Pass ]
 fast/forms/select/mac-wk2 [ Pass ]
 fast/sandbox/mac [ Pass ]
 fast/scrolling/mac [ Pass ]
diff --git a/LayoutTests/platform/mac-wk2/fast/forms/time/time-appearance-basic-expected.txt b/LayoutTests/platform/mac-wk2/fast/forms/time/time-appearance-basic-expected.txt
index 2dd0ad5..331034b 100644
--- a/LayoutTests/platform/mac-wk2/fast/forms/time/time-appearance-basic-expected.txt
+++ b/LayoutTests/platform/mac-wk2/fast/forms/time/time-appearance-basic-expected.txt
@@ -4,7 +4,7 @@
 step=60  
 step=3600  
 step=86400  
-step mismatched 
+step mismatched  
 RTL 
 Disabled, step=3600  
 Readonly, step=3600  
diff --git a/LayoutTests/platform/mac-wk2/fast/forms/time/time-input-rendering-basic-expected.txt b/LayoutTests/platform/mac-wk2/fast/forms/time/time-input-rendering-basic-expected.txt
index a9207e9..db48271 100644
--- a/LayoutTests/platform/mac-wk2/fast/forms/time/time-input-rendering-basic-expected.txt
+++ b/LayoutTests/platform/mac-wk2/fast/forms/time/time-input-rendering-basic-expected.txt
@@ -3,10 +3,46 @@
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderText {#text} at (110,0) size 4x18
-        text run at (110,0) width 4: " "
+      RenderText {#text} at (110,2) size 4x18
+        text run at (110,2) width 4: " "
       RenderText {#text} at (0,0) size 0x0
-layer at (8,19) size 110x6 clip at (10,21) size 106x2
-  RenderFlexibleBox {INPUT} at (0,11) size 110x6 [bgcolor=#FFFFFF] [border: (2px inset #000000)]
-layer at (122,19) size 110x6 clip at (124,21) size 106x2
-  RenderFlexibleBox {INPUT} at (114,11) size 110x6 [bgcolor=#FFFFFF] [border: (2px inset #000000)]
+layer at (8,10) size 110x19 clip at (10,12) size 106x15
+  RenderFlexibleBox {INPUT} at (0,2) size 110x19 [bgcolor=#FFFFFF] [border: (2px inset #000000)]
+layer at (11,13) size 56x13 scrollHeight 14
+  RenderBlock {DIV} at (3,3) size 56x13
+    RenderBlock {DIV} at (0,0) size 56x13
+      RenderInline {DIV} at (0,0) size 17x15
+        RenderText {#text} at (1,0) size 15x13
+          text run at (1,0) width 15: "09"
+      RenderInline {DIV} at (0,0) size 4x13
+        RenderText {#text} at (16,0) size 4x13
+          text run at (16,0) width 4: ":"
+      RenderInline {DIV} at (0,0) size 15x15
+        RenderText {#text} at (20,0) size 13x13
+          text run at (20,0) width 13: "41"
+      RenderInline {DIV} at (0,0) size 4x13
+        RenderText {#text} at (33,0) size 4x13
+          text run at (33,0) width 4: " "
+      RenderInline {DIV} at (0,0) size 20x15
+        RenderText {#text} at (37,0) size 18x13
+          text run at (37,0) width 18: "AM"
+layer at (122,10) size 110x19 clip at (124,12) size 106x15
+  RenderFlexibleBox {INPUT} at (114,2) size 110x19 [bgcolor=#FFFFFF] [border: (2px inset #000000)]
+layer at (173,13) size 56x13 scrollHeight 14
+  RenderBlock {DIV} at (51,3) size 56x13
+    RenderBlock {DIV} at (0,0) size 56x13
+      RenderInline {DIV} at (0,0) size 17x15
+        RenderText {#text} at (23,0) size 15x13
+          text run at (23,0) width 15: "09"
+      RenderInline {DIV} at (0,0) size 4x13
+        RenderText {#text} at (38,0) size 4x13
+          text run at (38,0) width 4: ":"
+      RenderInline {DIV} at (0,0) size 15x15
+        RenderText {#text} at (42,0) size 13x13
+          text run at (42,0) width 13: "41"
+      RenderInline {DIV} at (0,0) size 4x13
+        RenderText {#text} at (19,0) size 4x13
+          text run at (19,0) width 4 RTL: " "
+      RenderInline {DIV} at (0,0) size 20x15
+        RenderText {#text} at (1,0) size 18x13
+          text run at (1,0) width 18: "AM"
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 4bda98a..e700b0b 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,117 @@
+2020-09-09  Aditya Keerthi  <akeerthi@apple.com>
+
+        [macOS] Add editability to input type=time
+        https://bugs.webkit.org/show_bug.cgi?id=216188
+
+        Reviewed by Devin Rousso.
+
+        This patch adds editability to input type=time by leveraging existing
+        logic to add editable components to date/time inputs.
+
+        DateTime{Hour|Minute|Second|Millisecond|Meridiem}FieldElements were
+        created to represent the new editable fields. By default, only the
+        hour and minute fields are displayed. However, the millisecond
+        and second fields may be added depending on the initial value of
+        the element, or the value of the step attribute.
+
+        Tests: fast/forms/time/time-editable-components/time-editable-components-focus-and-blur-events.html
+               fast/forms/time/time-editable-components/time-editable-components-keyboard-events.html
+               fast/forms/time/time-editable-components/time-editable-components-mouse-events.html
+               fast/forms/time/time-editable-components/time-editable-components-second-and-millisecond-field.html
+
+        * css/html.css:
+
+        Update stylesheet to handle hour, minute, second, millisecond and meridiem fields.
+
+        (input::-webkit-datetime-edit-fields-wrapper):
+        (input::-webkit-datetime-edit-year-field,):
+        (input::-webkit-datetime-edit-year-field:focus,):
+        (input[disabled]::-webkit-datetime-edit-year-field,):
+        * html/BaseChooserOnlyDateAndTimeInputType.cpp:
+        (WebCore::DateTimeFormatValidator::visitField):
+        (WebCore::BaseChooserOnlyDateAndTimeInputType::updateInnerTextValue):
+        (WebCore::BaseChooserOnlyDateAndTimeInputType::attributeChanged):
+
+        The step attribute can determine whether the second and/or millisecond
+        fields are displayed. Consequently, we should update the fields in
+        m_dateTimeEditElement when the step attribute is changed.
+
+        * html/BaseChooserOnlyDateAndTimeInputType.h:
+
+        setupLayoutParameters() now takes an additional DateComponents argument.
+        This argument is needed to determine whether the second and/or
+        millisecond field is displayed.
+
+        * html/BaseDateAndTimeInputType.h:
+        * html/DateInputType.cpp:
+        (WebCore::DateInputType::setupLayoutParameters const):
+        * html/DateInputType.h:
+        * html/DateTimeFieldsState.h:
+        * html/DateTimeLocalInputType.cpp:
+        (WebCore::DateTimeLocalInputType::setupLayoutParameters const):
+        * html/DateTimeLocalInputType.h:
+        * html/MonthInputType.cpp:
+        (WebCore::MonthInputType::setupLayoutParameters const):
+        * html/MonthInputType.h:
+        * html/TimeInputType.cpp:
+        (WebCore::TimeInputType::isValidFormat const):
+        (WebCore::TimeInputType::formatDateTimeFieldsState const):
+        (WebCore::TimeInputType::setupLayoutParameters const):
+
+        The millisecond field is displayed if the date has a non-zero value for
+        milliseconds, or if the step attribute has sub-second precision. The
+        second field is displayed if the millisecond field is displayed, if the
+        date has a non-zero value for seconds, or if the step attribute has
+        sub-minute precision.
+
+        * html/TimeInputType.h:
+        * html/WeekInputType.cpp:
+        (WebCore::WeekInputType::setupLayoutParameters const):
+        * html/WeekInputType.h:
+        * html/shadow/DateTimeEditElement.cpp:
+        (WebCore::DateTimeEditBuilder::visitField): Updated to add new field types to the element.
+        * html/shadow/DateTimeEditElement.h:
+        * html/shadow/DateTimeFieldElements.cpp:
+        (WebCore::DateTimeHourFieldElement::DateTimeHourFieldElement):
+        (WebCore::DateTimeHourFieldElement::create):
+        (WebCore::DateTimeHourFieldElement::populateDateTimeFieldsState):
+        (WebCore::DateTimeHourFieldElement::setValueAsDate):
+        (WebCore::DateTimeMeridiemFieldElement::DateTimeMeridiemFieldElement):
+        (WebCore::DateTimeMeridiemFieldElement::create):
+        (WebCore::DateTimeMeridiemFieldElement::populateDateTimeFieldsState):
+        (WebCore::DateTimeMeridiemFieldElement::setValueAsDate):
+        (WebCore::DateTimeMillisecondFieldElement::DateTimeMillisecondFieldElement):
+        (WebCore::DateTimeMillisecondFieldElement::create):
+        (WebCore::DateTimeMillisecondFieldElement::populateDateTimeFieldsState):
+        (WebCore::DateTimeMillisecondFieldElement::setValueAsDate):
+        (WebCore::DateTimeMinuteFieldElement::DateTimeMinuteFieldElement):
+        (WebCore::DateTimeMinuteFieldElement::create):
+        (WebCore::DateTimeMinuteFieldElement::populateDateTimeFieldsState):
+        (WebCore::DateTimeMinuteFieldElement::setValueAsDate):
+        (WebCore::DateTimeSecondFieldElement::DateTimeSecondFieldElement):
+        (WebCore::DateTimeSecondFieldElement::create):
+        (WebCore::DateTimeSecondFieldElement::populateDateTimeFieldsState):
+        (WebCore::DateTimeSecondFieldElement::setValueAsDate):
+        * html/shadow/DateTimeFieldElements.h:
+        * html/shadow/DateTimeNumericFieldElement.cpp:
+        (WebCore::DateTimeNumericFieldElement::maximum const):
+        * html/shadow/DateTimeNumericFieldElement.h:
+        * html/shadow/DateTimeSymbolicFieldElement.cpp:
+        (WebCore::DateTimeSymbolicFieldElement::DateTimeSymbolicFieldElement):
+        (WebCore::DateTimeSymbolicFieldElement::handleKeyboardEvent): Implement editing using the same typeahead behavior as <select> elements.
+        (WebCore::DateTimeSymbolicFieldElement::indexOfSelectedOption const):
+        (WebCore::DateTimeSymbolicFieldElement::optionCount const):
+        (WebCore::DateTimeSymbolicFieldElement::optionAtIndex const):
+        * html/shadow/DateTimeSymbolicFieldElement.h:
+        * platform/text/PlatformLocale.cpp:
+        (WebCore::Locale::localizedDecimalSeparator):
+
+        Added method to ensure the correct decimal separator is displayed
+        depending on the user's locale. This separator is used when
+        the millisecond field is present.
+
+        * platform/text/PlatformLocale.h:
+
 2020-09-08  Ryosuke Niwa  <rniwa@webkit.org>
 
         Node flags should be an OptionSet
diff --git a/Source/WebCore/css/html.css b/Source/WebCore/css/html.css
index c580ad2..436cf97 100644
--- a/Source/WebCore/css/html.css
+++ b/Source/WebCore/css/html.css
@@ -497,18 +497,29 @@
 
 input::-webkit-datetime-edit-fields-wrapper {
     display: inline-block;
+    white-space: pre;
 }
 
 input::-webkit-datetime-edit-year-field,
 input::-webkit-datetime-edit-month-field,
-input::-webkit-datetime-edit-day-field {
+input::-webkit-datetime-edit-day-field,
+input::-webkit-datetime-edit-hour-field,
+input::-webkit-datetime-edit-minute-field,
+input::-webkit-datetime-edit-second-field,
+input::-webkit-datetime-edit-millisecond-field,
+input::-webkit-datetime-edit-meridiem-field {
     display: inline;
     padding: 1px;
 }
 
 input::-webkit-datetime-edit-year-field:focus,
 input::-webkit-datetime-edit-month-field:focus,
-input::-webkit-datetime-edit-day-field:focus {
+input::-webkit-datetime-edit-day-field:focus,
+input::-webkit-datetime-edit-hour-field:focus,
+input::-webkit-datetime-edit-minute-field:focus,
+input::-webkit-datetime-edit-second-field:focus,
+input::-webkit-datetime-edit-millisecond-field:focus,
+input::-webkit-datetime-edit-meridiem-field:focus {
 #if defined(WTF_PLATFORM_COCOA) && WTF_PLATFORM_COCOA
     background-color: -apple-system-control-accent;
     color: white;
@@ -522,6 +533,11 @@
 input[disabled]::-webkit-datetime-edit-year-field,
 input[disabled]::-webkit-datetime-edit-month-field,
 input[disabled]::-webkit-datetime-edit-day-field,
+input[disabled]::-webkit-datetime-edit-hour-field,
+input[disabled]::-webkit-datetime-edit-minute-field,
+input[disabled]::-webkit-datetime-edit-second-field,
+input[disabled]::-webkit-datetime-edit-millisecond-field,
+input[disabled]::-webkit-datetime-edit-meridiem-field,
 input[disabled]::-webkit-datetime-edit-text {
     color: GrayText;
 }
diff --git a/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp b/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp
index 8d8783f..b5a488c 100644
--- a/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp
+++ b/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp
@@ -40,6 +40,7 @@
 #include "RenderElement.h"
 #include "Settings.h"
 #include "ShadowRoot.h"
+#include "StepRange.h"
 #include "Text.h"
 #include "UserGestureIndicator.h"
 #include <wtf/NeverDestroyed.h>
@@ -80,7 +81,7 @@
         break;
 
     case DateTimeFormat::FieldTypePeriod:
-        m_results.add(DateTimeFormatValidationResults::HasAMPM);
+        m_results.add(DateTimeFormatValidationResults::HasMeridiem);
         break;
 
     case DateTimeFormat::FieldTypeHour11:
@@ -91,7 +92,7 @@
     case DateTimeFormat::FieldTypeHour23:
     case DateTimeFormat::FieldTypeHour24:
         m_results.add(DateTimeFormatValidationResults::HasHour);
-        m_results.add(DateTimeFormatValidationResults::HasAMPM);
+        m_results.add(DateTimeFormatValidationResults::HasMeridiem);
         break;
 
     case DateTimeFormat::FieldTypeMinute:
@@ -192,12 +193,21 @@
     }
 
     DateTimeEditElement::LayoutParameters layoutParameters(element()->locale());
-    setupLayoutParameters(layoutParameters);
+
+    auto date = parseToDateComponents(element()->value());
+    if (date)
+        setupLayoutParameters(layoutParameters, *date);
+    else {
+        if (auto dateForLayout = setMillisecondToDateComponents(createStepRange(AnyStepHandling::Default).minimum().toDouble()))
+            setupLayoutParameters(layoutParameters, *dateForLayout);
+        else
+            setupLayoutParameters(layoutParameters, DateComponents());
+    }
 
     if (!DateTimeFormatValidator().validateFormat(layoutParameters.dateTimeFormat, *this))
         layoutParameters.dateTimeFormat = layoutParameters.fallbackDateTimeFormat;
 
-    if (auto date = parseToDateComponents(element()->value()))
+    if (date)
         m_dateTimeEditElement->setValueAsDate(layoutParameters, *date);
     else
         m_dateTimeEditElement->setEmptyValue(layoutParameters);
@@ -298,7 +308,9 @@
             if (!element->hasDirtyValue())
                 updateInnerTextValue();
         }
-    }
+    } else if (name == stepAttr && m_dateTimeEditElement)
+        updateInnerTextValue();
+
     BaseDateAndTimeInputType::attributeChanged(name);
 }
 
diff --git a/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h b/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h
index c2bd080..2f15f348 100644
--- a/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h
+++ b/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h
@@ -42,7 +42,7 @@
     HasMonth = 1 << 1,
     HasWeek = 1 << 2,
     HasDay = 1 << 3,
-    HasAMPM = 1 << 4,
+    HasMeridiem = 1 << 4,
     HasHour = 1 << 5,
     HasMinute = 1 << 6,
     HasSecond = 1 << 7,
@@ -56,7 +56,7 @@
     explicit BaseChooserOnlyDateAndTimeInputType(HTMLInputElement& element) : BaseDateAndTimeInputType(element) { }
     ~BaseChooserOnlyDateAndTimeInputType();
 
-    virtual void setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const = 0;
+    virtual void setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const = 0;
 
 private:
     void updateInnerTextValue() override;
diff --git a/Source/WebCore/html/BaseDateAndTimeInputType.h b/Source/WebCore/html/BaseDateAndTimeInputType.h
index 1fce509..fe85955 100644
--- a/Source/WebCore/html/BaseDateAndTimeInputType.h
+++ b/Source/WebCore/html/BaseDateAndTimeInputType.h
@@ -53,10 +53,9 @@
     bool isKeyboardFocusable(KeyboardEvent*) const override;
 
     virtual Optional<DateComponents> parseToDateComponents(const StringView&) const = 0;
-
-private:
     virtual Optional<DateComponents> setMillisecondToDateComponents(double) const = 0;
 
+private:
     double valueAsDate() const override;
     ExceptionOr<void> setValueAsDate(double) const override;
     double valueAsDouble() const override;
diff --git a/Source/WebCore/html/DateInputType.cpp b/Source/WebCore/html/DateInputType.cpp
index 6500480..cd5e357 100644
--- a/Source/WebCore/html/DateInputType.cpp
+++ b/Source/WebCore/html/DateInputType.cpp
@@ -102,7 +102,7 @@
     return makeString(pad('0', 4, *state.year), '-', pad('0', 2, *state.month), '-', pad('0', 2, *state.dayOfMonth));
 }
 
-void DateInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters& layoutParameters) const
+void DateInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters& layoutParameters, const DateComponents&) const
 {
     layoutParameters.dateTimeFormat = layoutParameters.locale.dateFormat();
     layoutParameters.fallbackDateTimeFormat = "yyyy-MM-dd"_s;
diff --git a/Source/WebCore/html/DateInputType.h b/Source/WebCore/html/DateInputType.h
index f529a64..648f6ee 100644
--- a/Source/WebCore/html/DateInputType.h
+++ b/Source/WebCore/html/DateInputType.h
@@ -50,7 +50,7 @@
 
     bool isValidFormat(OptionSet<DateTimeFormatValidationResults>) const final;
     String formatDateTimeFieldsState(const DateTimeFieldsState&) const final;
-    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const final;
+    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const final;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/DateTimeFieldsState.h b/Source/WebCore/html/DateTimeFieldsState.h
index 64f8548..fe615e2 100644
--- a/Source/WebCore/html/DateTimeFieldsState.h
+++ b/Source/WebCore/html/DateTimeFieldsState.h
@@ -33,9 +33,19 @@
 namespace WebCore {
 
 struct DateTimeFieldsState {
+    enum class Meridiem : bool {
+        AM,
+        PM,
+    };
+
     Optional<unsigned> year;
     Optional<unsigned> month;
     Optional<unsigned> dayOfMonth;
+    Optional<unsigned> hour;
+    Optional<unsigned> minute;
+    Optional<unsigned> second;
+    Optional<unsigned> millisecond;
+    Optional<Meridiem> meridiem;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/DateTimeLocalInputType.cpp b/Source/WebCore/html/DateTimeLocalInputType.cpp
index ccb2c32..0682261 100644
--- a/Source/WebCore/html/DateTimeLocalInputType.cpp
+++ b/Source/WebCore/html/DateTimeLocalInputType.cpp
@@ -106,7 +106,7 @@
     return emptyString();
 }
 
-void DateTimeLocalInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const
+void DateTimeLocalInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const
 {
 }
 
diff --git a/Source/WebCore/html/DateTimeLocalInputType.h b/Source/WebCore/html/DateTimeLocalInputType.h
index e634742..1318cef 100644
--- a/Source/WebCore/html/DateTimeLocalInputType.h
+++ b/Source/WebCore/html/DateTimeLocalInputType.h
@@ -52,7 +52,7 @@
 
     bool isValidFormat(OptionSet<DateTimeFormatValidationResults>) const final;
     String formatDateTimeFieldsState(const DateTimeFieldsState&) const final;
-    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const final;
+    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const final;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/MonthInputType.cpp b/Source/WebCore/html/MonthInputType.cpp
index 5ef4c89..8a49210 100644
--- a/Source/WebCore/html/MonthInputType.cpp
+++ b/Source/WebCore/html/MonthInputType.cpp
@@ -144,7 +144,7 @@
     return emptyString();
 }
 
-void MonthInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const
+void MonthInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const
 {
 }
 
diff --git a/Source/WebCore/html/MonthInputType.h b/Source/WebCore/html/MonthInputType.h
index f489b70..941651f 100644
--- a/Source/WebCore/html/MonthInputType.h
+++ b/Source/WebCore/html/MonthInputType.h
@@ -55,7 +55,7 @@
 
     bool isValidFormat(OptionSet<DateTimeFormatValidationResults>) const final;
     String formatDateTimeFieldsState(const DateTimeFieldsState&) const final;
-    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const final;
+    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const final;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/TimeInputType.cpp b/Source/WebCore/html/TimeInputType.cpp
index 2fe6555..4bf2e87 100644
--- a/Source/WebCore/html/TimeInputType.cpp
+++ b/Source/WebCore/html/TimeInputType.cpp
@@ -33,6 +33,7 @@
 #include "TimeInputType.h"
 
 #include "DateComponents.h"
+#include "DateTimeFieldsState.h"
 #include "Decimal.h"
 #include "HTMLInputElement.h"
 #include "HTMLNames.h"
@@ -112,16 +113,48 @@
 
 bool TimeInputType::isValidFormat(OptionSet<DateTimeFormatValidationResults> results) const
 {
-    return results.containsAll({ DateTimeFormatValidationResults::HasHour, DateTimeFormatValidationResults::HasMinute });
+    return results.containsAll({ DateTimeFormatValidationResults::HasHour, DateTimeFormatValidationResults::HasMinute, DateTimeFormatValidationResults::HasMeridiem });
 }
 
-String TimeInputType::formatDateTimeFieldsState(const DateTimeFieldsState&) const
+String TimeInputType::formatDateTimeFieldsState(const DateTimeFieldsState& state) const
 {
-    return emptyString();
+    if (!state.hour || !state.minute || !state.meridiem)
+        return emptyString();
+
+    unsigned hour23 = (*state.hour % 12) + (*state.meridiem == DateTimeFieldsState::Meridiem::PM ? 12 : 0);
+    auto hourMinuteString = makeString(pad('0', 2, hour23), ':', pad('0', 2, *state.minute));
+
+    if (state.millisecond)
+        return makeString(hourMinuteString, ':', pad('0', 2, state.second ? *state.second : 0), '.', pad('0', 3, *state.millisecond));
+
+    if (state.second)
+        return makeString(hourMinuteString, ':', pad('0', 2, *state.second));
+
+    return hourMinuteString;
 }
 
-void TimeInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const
+void TimeInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters& layoutParameters, const DateComponents& date) const
 {
+    auto stepRange = createStepRange(AnyStepHandling::Default);
+    auto millisecondsPerSecond = Decimal::fromDouble(msPerSecond);
+    auto millisecondsPerMinute = Decimal::fromDouble(msPerMinute);
+
+    layoutParameters.shouldHaveMillisecondField = date.millisecond()
+        || !stepRange.minimum().remainder(millisecondsPerSecond).isZero()
+        || !stepRange.step().remainder(millisecondsPerSecond).isZero();
+
+    bool shouldHaveSecondField = layoutParameters.shouldHaveMillisecondField
+        || date.second()
+        || !stepRange.minimum().remainder(millisecondsPerMinute).isZero()
+        || !stepRange.step().remainder(millisecondsPerMinute).isZero();
+
+    if (shouldHaveSecondField) {
+        layoutParameters.dateTimeFormat = layoutParameters.locale.timeFormat();
+        layoutParameters.fallbackDateTimeFormat = "HH:mm:ss"_s;
+    } else {
+        layoutParameters.dateTimeFormat = layoutParameters.locale.shortTimeFormat();
+        layoutParameters.fallbackDateTimeFormat = "HH:mm"_s;
+    }
 }
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/TimeInputType.h b/Source/WebCore/html/TimeInputType.h
index ac824fd..2d88fad 100644
--- a/Source/WebCore/html/TimeInputType.h
+++ b/Source/WebCore/html/TimeInputType.h
@@ -52,7 +52,7 @@
 
     bool isValidFormat(OptionSet<DateTimeFormatValidationResults>) const final;
     String formatDateTimeFieldsState(const DateTimeFieldsState&) const final;
-    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const final;
+    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const final;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/WeekInputType.cpp b/Source/WebCore/html/WeekInputType.cpp
index bdc92a1..1ff66c3 100644
--- a/Source/WebCore/html/WeekInputType.cpp
+++ b/Source/WebCore/html/WeekInputType.cpp
@@ -97,7 +97,7 @@
     return emptyString();
 }
 
-void WeekInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const
+void WeekInputType::setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const
 {
 }
 
diff --git a/Source/WebCore/html/WeekInputType.h b/Source/WebCore/html/WeekInputType.h
index 00a31a5..bfeb71a 100644
--- a/Source/WebCore/html/WeekInputType.h
+++ b/Source/WebCore/html/WeekInputType.h
@@ -51,7 +51,7 @@
 
     bool isValidFormat(OptionSet<DateTimeFormatValidationResults>) const final;
     String formatDateTimeFieldsState(const DateTimeFieldsState&) const final;
-    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&) const final;
+    void setupLayoutParameters(DateTimeEditElement::LayoutParameters&, const DateComponents&) const final;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/shadow/DateTimeEditElement.cpp b/Source/WebCore/html/shadow/DateTimeEditElement.cpp
index a97c54d..8fadd17 100644
--- a/Source/WebCore/html/shadow/DateTimeEditElement.cpp
+++ b/Source/WebCore/html/shadow/DateTimeEditElement.cpp
@@ -85,6 +85,36 @@
         return;
     }
 
+    case DateTimeFormat::FieldTypeFractionalSecond: {
+        m_editElement.addField(DateTimeMillisecondFieldElement::create(document, m_editElement));
+        return;
+    }
+
+    case DateTimeFormat::FieldTypeHour11: {
+        m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 0, 11));
+        return;
+    }
+
+    case DateTimeFormat::FieldTypeHour12: {
+        m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 1, 12));
+        return;
+    }
+
+    case DateTimeFormat::FieldTypeHour23: {
+        m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 0, 23));
+        return;
+    }
+
+    case DateTimeFormat::FieldTypeHour24: {
+        m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 1, 24));
+        return;
+    }
+
+    case DateTimeFormat::FieldTypeMinute: {
+        m_editElement.addField(DateTimeMinuteFieldElement::create(document, m_editElement));
+        return;
+    }
+
     case DateTimeFormat::FieldTypeMonth:
     case DateTimeFormat::FieldTypeMonthStandAlone: {
         constexpr int countForAbbreviatedMonth = 3;
@@ -109,6 +139,21 @@
         }
     }
 
+    case DateTimeFormat::FieldTypePeriod: {
+        m_editElement.addField(DateTimeMeridiemFieldElement::create(document, m_editElement, m_parameters.locale.timeAMPMLabels()));
+        return;
+    }
+
+    case DateTimeFormat::FieldTypeSecond: {
+        m_editElement.addField(DateTimeSecondFieldElement::create(document, m_editElement));
+
+        if (m_parameters.shouldHaveMillisecondField) {
+            visitLiteral(m_parameters.locale.localizedDecimalSeparator());
+            visitField(DateTimeFormat::FieldTypeFractionalSecond, 3);
+        }
+        return;
+    }
+
     case DateTimeFormat::FieldTypeYear: {
         m_editElement.addField(DateTimeYearFieldElement::create(document, m_editElement));
         return;
diff --git a/Source/WebCore/html/shadow/DateTimeEditElement.h b/Source/WebCore/html/shadow/DateTimeEditElement.h
index 0bcca6a..d796496 100644
--- a/Source/WebCore/html/shadow/DateTimeEditElement.h
+++ b/Source/WebCore/html/shadow/DateTimeEditElement.h
@@ -55,6 +55,7 @@
         String dateTimeFormat;
         String fallbackDateTimeFormat;
         Locale& locale;
+        bool shouldHaveMillisecondField { false };
 
         LayoutParameters(Locale& locale)
             : locale(locale)
diff --git a/Source/WebCore/html/shadow/DateTimeFieldElements.cpp b/Source/WebCore/html/shadow/DateTimeFieldElements.cpp
index f5cc9ee..5e0c681 100644
--- a/Source/WebCore/html/shadow/DateTimeFieldElements.cpp
+++ b/Source/WebCore/html/shadow/DateTimeFieldElements.cpp
@@ -60,6 +60,145 @@
     setValueAsInteger(date.monthDay());
 }
 
+WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeHourFieldElement);
+
+DateTimeHourFieldElement::DateTimeHourFieldElement(Document& document, FieldOwner& fieldOwner, int minimum, int maximum)
+    : DateTimeNumericFieldElement(document, fieldOwner, Range(minimum, maximum), "--"_s)
+{
+    static MainThreadNeverDestroyed<const AtomString> hourPseudoId("-webkit-datetime-edit-hour-field", AtomString::ConstructFromLiteral);
+    initialize(hourPseudoId);
+}
+
+Ref<DateTimeHourFieldElement> DateTimeHourFieldElement::create(Document& document, FieldOwner& fieldOwner, int minimum, int maximum)
+{
+    return adoptRef(*new DateTimeHourFieldElement(document, fieldOwner, minimum, maximum));
+}
+
+void DateTimeHourFieldElement::populateDateTimeFieldsState(DateTimeFieldsState& state)
+{
+    if (!hasValue())
+        return;
+
+    int value = valueAsInteger();
+
+    switch (maximum()) {
+    case 11:
+        state.hour = value ?: 12;
+        return;
+    case 12:
+        state.hour = value;
+        return;
+    case 23:
+        state.hour = (value % 12) ?: 12;
+        state.meridiem = value >= 12 ? DateTimeFieldsState::Meridiem::PM : DateTimeFieldsState::Meridiem::AM;
+        return;
+    case 24:
+        if (value == 24) {
+            state.hour = 12;
+            state.meridiem = DateTimeFieldsState::Meridiem::AM;
+            return;
+        }
+        state.hour = (value % 12) ?: 12;
+        state.meridiem = value >= 12 ? DateTimeFieldsState::Meridiem::PM : DateTimeFieldsState::Meridiem::AM;
+        return;
+    }
+}
+
+void DateTimeHourFieldElement::setValueAsDate(const DateComponents& date)
+{
+    int hour = date.hour();
+
+    switch (maximum()) {
+    case 11:
+        setValueAsInteger(hour % 12);
+        return;
+    case 12:
+        setValueAsInteger((hour % 12) ?: 12);
+        return;
+    case 23:
+        setValueAsInteger(hour);
+        return;
+    case 24:
+        setValueAsInteger(hour + 1);
+        return;
+    }
+}
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeMeridiemFieldElement);
+
+DateTimeMeridiemFieldElement::DateTimeMeridiemFieldElement(Document& document, FieldOwner& fieldOwner, const Vector<String>& labels)
+    : DateTimeSymbolicFieldElement(document, fieldOwner, labels)
+{
+    static MainThreadNeverDestroyed<const AtomString> meridiemPseudoId("-webkit-datetime-edit-meridiem-field", AtomString::ConstructFromLiteral);
+    initialize(meridiemPseudoId);
+}
+
+Ref<DateTimeMeridiemFieldElement> DateTimeMeridiemFieldElement::create(Document& document, FieldOwner& fieldOwner, const Vector<String>& labels)
+{
+    return adoptRef(*new DateTimeMeridiemFieldElement(document, fieldOwner, labels));
+}
+
+void DateTimeMeridiemFieldElement::populateDateTimeFieldsState(DateTimeFieldsState& state)
+{
+    if (hasValue())
+        state.meridiem = valueAsInteger() ? DateTimeFieldsState::Meridiem::PM : DateTimeFieldsState::Meridiem::AM;
+}
+
+void DateTimeMeridiemFieldElement::setValueAsDate(const DateComponents& date)
+{
+    setValueAsInteger(date.hour() >= 12 ? 1 : 0);
+}
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeMillisecondFieldElement);
+
+DateTimeMillisecondFieldElement::DateTimeMillisecondFieldElement(Document& document, FieldOwner& fieldOwner)
+    : DateTimeNumericFieldElement(document, fieldOwner, Range(0, 999), "--"_s)
+{
+    static MainThreadNeverDestroyed<const AtomString> millisecondPseudoId("-webkit-datetime-edit-millisecond-field", AtomString::ConstructFromLiteral);
+    initialize(millisecondPseudoId);
+}
+
+Ref<DateTimeMillisecondFieldElement> DateTimeMillisecondFieldElement::create(Document& document, FieldOwner& fieldOwner)
+{
+    return adoptRef(*new DateTimeMillisecondFieldElement(document, fieldOwner));
+}
+
+void DateTimeMillisecondFieldElement::populateDateTimeFieldsState(DateTimeFieldsState& state)
+{
+    if (hasValue())
+        state.millisecond = valueAsInteger();
+}
+
+void DateTimeMillisecondFieldElement::setValueAsDate(const DateComponents& date)
+{
+    setValueAsInteger(date.millisecond());
+}
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeMinuteFieldElement);
+
+DateTimeMinuteFieldElement::DateTimeMinuteFieldElement(Document& document, FieldOwner& fieldOwner)
+    : DateTimeNumericFieldElement(document, fieldOwner, Range(0, 59), "--"_s)
+{
+    static MainThreadNeverDestroyed<const AtomString> minutePseudoId("-webkit-datetime-edit-minute-field", AtomString::ConstructFromLiteral);
+    initialize(minutePseudoId);
+}
+
+Ref<DateTimeMinuteFieldElement> DateTimeMinuteFieldElement::create(Document& document, FieldOwner& fieldOwner)
+{
+    return adoptRef(*new DateTimeMinuteFieldElement(document, fieldOwner));
+}
+
+void DateTimeMinuteFieldElement::populateDateTimeFieldsState(DateTimeFieldsState& state)
+{
+    if (hasValue())
+        state.minute = valueAsInteger();
+}
+
+void DateTimeMinuteFieldElement::setValueAsDate(const DateComponents& date)
+{
+    setValueAsInteger(date.minute());
+}
+
 WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeMonthFieldElement);
 
 DateTimeMonthFieldElement::DateTimeMonthFieldElement(Document& document, FieldOwner& fieldOwner)
@@ -86,6 +225,31 @@
     setValueAsInteger(date.month() + 1);
 }
 
+WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeSecondFieldElement);
+
+DateTimeSecondFieldElement::DateTimeSecondFieldElement(Document& document, FieldOwner& fieldOwner)
+    : DateTimeNumericFieldElement(document, fieldOwner, Range(0, 59), "--"_s)
+{
+    static MainThreadNeverDestroyed<const AtomString> secondPseudoId("-webkit-datetime-edit-second-field", AtomString::ConstructFromLiteral);
+    initialize(secondPseudoId);
+}
+
+Ref<DateTimeSecondFieldElement> DateTimeSecondFieldElement::create(Document& document, FieldOwner& fieldOwner)
+{
+    return adoptRef(*new DateTimeSecondFieldElement(document, fieldOwner));
+}
+
+void DateTimeSecondFieldElement::populateDateTimeFieldsState(DateTimeFieldsState& state)
+{
+    if (hasValue())
+        state.second = valueAsInteger();
+}
+
+void DateTimeSecondFieldElement::setValueAsDate(const DateComponents& date)
+{
+    setValueAsInteger(date.second());
+}
+
 WTF_MAKE_ISO_ALLOCATED_IMPL(DateTimeSymbolicMonthFieldElement);
 
 DateTimeSymbolicMonthFieldElement::DateTimeSymbolicMonthFieldElement(Document& document, FieldOwner& fieldOwner, const Vector<String>& labels)
diff --git a/Source/WebCore/html/shadow/DateTimeFieldElements.h b/Source/WebCore/html/shadow/DateTimeFieldElements.h
index f110378..d765efb 100644
--- a/Source/WebCore/html/shadow/DateTimeFieldElements.h
+++ b/Source/WebCore/html/shadow/DateTimeFieldElements.h
@@ -47,6 +47,62 @@
     void populateDateTimeFieldsState(DateTimeFieldsState&);
 };
 
+class DateTimeHourFieldElement final : public DateTimeNumericFieldElement {
+    WTF_MAKE_ISO_ALLOCATED(DateTimeHourFieldElement);
+
+public:
+    static Ref<DateTimeHourFieldElement> create(Document&, FieldOwner&, int minimum, int maximum);
+
+private:
+    DateTimeHourFieldElement(Document&, FieldOwner&, int minimum, int maximum);
+
+    // DateTimeFieldElement functions:
+    virtual void populateDateTimeFieldsState(DateTimeFieldsState&);
+    virtual void setValueAsDate(const DateComponents&);
+};
+
+class DateTimeMeridiemFieldElement final : public DateTimeSymbolicFieldElement {
+    WTF_MAKE_ISO_ALLOCATED(DateTimeMeridiemFieldElement);
+
+public:
+    static Ref<DateTimeMeridiemFieldElement> create(Document&, FieldOwner&, const Vector<String>&);
+
+private:
+    DateTimeMeridiemFieldElement(Document&, FieldOwner&, const Vector<String>&);
+
+    // DateTimeFieldElement functions:
+    void populateDateTimeFieldsState(DateTimeFieldsState&);
+    void setValueAsDate(const DateComponents&);
+};
+
+class DateTimeMillisecondFieldElement final : public DateTimeNumericFieldElement {
+    WTF_MAKE_ISO_ALLOCATED(DateTimeMillisecondFieldElement);
+
+public:
+    static Ref<DateTimeMillisecondFieldElement> create(Document&, FieldOwner&);
+
+private:
+    DateTimeMillisecondFieldElement(Document&, FieldOwner&);
+
+    // DateTimeFieldElement functions:
+    virtual void populateDateTimeFieldsState(DateTimeFieldsState&);
+    virtual void setValueAsDate(const DateComponents&);
+};
+
+class DateTimeMinuteFieldElement final : public DateTimeNumericFieldElement {
+    WTF_MAKE_ISO_ALLOCATED(DateTimeMinuteFieldElement);
+
+public:
+    static Ref<DateTimeMinuteFieldElement> create(Document&, FieldOwner&);
+
+private:
+    DateTimeMinuteFieldElement(Document&, FieldOwner&);
+
+    // DateTimeFieldElement functions:
+    virtual void populateDateTimeFieldsState(DateTimeFieldsState&);
+    virtual void setValueAsDate(const DateComponents&);
+};
+
 class DateTimeMonthFieldElement final : public DateTimeNumericFieldElement {
     WTF_MAKE_ISO_ALLOCATED(DateTimeMonthFieldElement);
 
@@ -61,6 +117,20 @@
     void populateDateTimeFieldsState(DateTimeFieldsState&);
 };
 
+class DateTimeSecondFieldElement final : public DateTimeNumericFieldElement {
+    WTF_MAKE_ISO_ALLOCATED(DateTimeSecondFieldElement);
+
+public:
+    static Ref<DateTimeSecondFieldElement> create(Document&, FieldOwner&);
+
+private:
+    DateTimeSecondFieldElement(Document&, FieldOwner&);
+
+    // DateTimeFieldElement functions:
+    virtual void populateDateTimeFieldsState(DateTimeFieldsState&);
+    virtual void setValueAsDate(const DateComponents&);
+};
+
 class DateTimeSymbolicMonthFieldElement final : public DateTimeSymbolicFieldElement {
     WTF_MAKE_ISO_ALLOCATED(DateTimeSymbolicMonthFieldElement);
 
diff --git a/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp b/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp
index 5c77a7a..fc07247 100644
--- a/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp
+++ b/Source/WebCore/html/shadow/DateTimeNumericFieldElement.cpp
@@ -57,6 +57,11 @@
 {
 }
 
+int DateTimeNumericFieldElement::maximum() const
+{
+    return m_range.maximum;
+}
+
 String DateTimeNumericFieldElement::formatValue(int value) const
 {
     Locale& locale = localeForOwner();
diff --git a/Source/WebCore/html/shadow/DateTimeNumericFieldElement.h b/Source/WebCore/html/shadow/DateTimeNumericFieldElement.h
index 3eeb952..ab257ee 100644
--- a/Source/WebCore/html/shadow/DateTimeNumericFieldElement.h
+++ b/Source/WebCore/html/shadow/DateTimeNumericFieldElement.h
@@ -50,6 +50,8 @@
 protected:
     DateTimeNumericFieldElement(Document&, FieldOwner&, const Range&, const String& placeholder);
 
+    int maximum() const;
+
     // DateTimeFieldElement functions:
     bool hasValue() const final;
     void initialize(const AtomString&);
diff --git a/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.cpp b/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.cpp
index 22ebd89..cec7729 100644
--- a/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.cpp
+++ b/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.cpp
@@ -49,6 +49,7 @@
     : DateTimeFieldElement(document, fieldOwner)
     , m_symbols(symbols)
     , m_visibleEmptyValue(makeVisibleEmptyValue(symbols))
+    , m_typeAhead(this)
 {
     ASSERT(!m_symbols.isEmpty());
 }
@@ -111,9 +112,32 @@
     return hasValue() ? m_symbols[m_selectedIndex] : visibleEmptyValue();
 }
 
-void DateTimeSymbolicFieldElement::handleKeyboardEvent(KeyboardEvent&)
+void DateTimeSymbolicFieldElement::handleKeyboardEvent(KeyboardEvent& keyboardEvent)
 {
-    // FIXME: Implement after adding layout for <input type=time>.
+    if (keyboardEvent.type() != eventNames().keypressEvent)
+        return;
+
+    keyboardEvent.setDefaultHandled();
+
+    int index = m_typeAhead.handleEvent(&keyboardEvent, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar | TypeAhead::MatchIndex);
+    if (index < 0)
+        return;
+    setValueAsInteger(index, DispatchInputAndChangeEvents);
+}
+
+int DateTimeSymbolicFieldElement::indexOfSelectedOption() const
+{
+    return m_selectedIndex;
+}
+
+int DateTimeSymbolicFieldElement::optionCount() const
+{
+    return m_symbols.size();
+}
+
+String DateTimeSymbolicFieldElement::optionAtIndex(int index) const
+{
+    return m_symbols[index];
 }
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.h b/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.h
index 809de26..1e5addf 100644
--- a/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.h
+++ b/Source/WebCore/html/shadow/DateTimeSymbolicFieldElement.h
@@ -29,10 +29,11 @@
 #if ENABLE(DATE_AND_TIME_INPUT_TYPES)
 
 #include "DateTimeFieldElement.h"
+#include "TypeAhead.h"
 
 namespace WebCore {
 
-class DateTimeSymbolicFieldElement : public DateTimeFieldElement {
+class DateTimeSymbolicFieldElement : public DateTimeFieldElement, public TypeAheadDataSource {
     WTF_MAKE_ISO_ALLOCATED(DateTimeSymbolicFieldElement);
 protected:
     DateTimeSymbolicFieldElement(Document&, FieldOwner&, const Vector<String>&);
@@ -55,9 +56,15 @@
     String visibleValue() const final;
     void handleKeyboardEvent(KeyboardEvent&) final;
 
+    // TypeAheadDataSource functions:
+    int indexOfSelectedOption() const final;
+    int optionCount() const final;
+    String optionAtIndex(int index) const final;
+
     const Vector<String> m_symbols;
 
     const AtomString m_visibleEmptyValue;
+    TypeAhead m_typeAhead;
     int m_selectedIndex { invalidIndex };
 };
 
diff --git a/Source/WebCore/platform/text/PlatformLocale.cpp b/Source/WebCore/platform/text/PlatformLocale.cpp
index b5b0eea..f47200b 100644
--- a/Source/WebCore/platform/text/PlatformLocale.cpp
+++ b/Source/WebCore/platform/text/PlatformLocale.cpp
@@ -354,6 +354,12 @@
     }
     return builder.toString();
 }
+
+String Locale::localizedDecimalSeparator()
+{
+    initializeLocaleData();
+    return m_decimalSymbols[DecimalSeparatorIndex];
+}
 #endif
 
 }
diff --git a/Source/WebCore/platform/text/PlatformLocale.h b/Source/WebCore/platform/text/PlatformLocale.h
index e63c0ca..4bf075c 100644
--- a/Source/WebCore/platform/text/PlatformLocale.h
+++ b/Source/WebCore/platform/text/PlatformLocale.h
@@ -108,6 +108,8 @@
     // localized string of January, and the last item is a localized string of
     // December. These strings should not be abbreviations.
     virtual const Vector<String>& monthLabels() = 0;
+
+    String localizedDecimalSeparator();
 #endif
 
 #if ENABLE(DATE_AND_TIME_INPUT_TYPES)