1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
33#include "core/html/forms/BaseMultipleFieldsDateAndTimeInputType.h"
34
35#include "CSSValueKeywords.h"
36#include "RuntimeEnabledFeatures.h"
37#include "core/dom/shadow/ShadowRoot.h"
38#include "core/events/KeyboardEvent.h"
39#include "core/html/HTMLDataListElement.h"
40#include "core/html/HTMLInputElement.h"
41#include "core/html/HTMLOptionElement.h"
42#include "core/html/forms/DateTimeFieldsState.h"
43#include "core/html/forms/FormController.h"
44#include "core/html/shadow/ShadowElementNames.h"
45#include "core/page/FocusController.h"
46#include "core/page/Page.h"
47#include "core/rendering/RenderTheme.h"
48#include "platform/DateComponents.h"
49#include "platform/text/DateTimeFormat.h"
50#include "platform/text/PlatformLocale.h"
51#include "wtf/DateMath.h"
52
53namespace WebCore {
54
55class DateTimeFormatValidator : public DateTimeFormat::TokenHandler {
56public:
57    DateTimeFormatValidator()
58        : m_hasYear(false)
59        , m_hasMonth(false)
60        , m_hasWeek(false)
61        , m_hasDay(false)
62        , m_hasAMPM(false)
63        , m_hasHour(false)
64        , m_hasMinute(false)
65        , m_hasSecond(false) { }
66
67    virtual void visitField(DateTimeFormat::FieldType, int) OVERRIDE FINAL;
68    virtual void visitLiteral(const String&) OVERRIDE FINAL { }
69
70    bool validateFormat(const String& format, const BaseMultipleFieldsDateAndTimeInputType&);
71
72private:
73    bool m_hasYear;
74    bool m_hasMonth;
75    bool m_hasWeek;
76    bool m_hasDay;
77    bool m_hasAMPM;
78    bool m_hasHour;
79    bool m_hasMinute;
80    bool m_hasSecond;
81};
82
83void DateTimeFormatValidator::visitField(DateTimeFormat::FieldType fieldType, int)
84{
85    switch (fieldType) {
86    case DateTimeFormat::FieldTypeYear:
87        m_hasYear = true;
88        break;
89    case DateTimeFormat::FieldTypeMonth: // Fallthrough.
90    case DateTimeFormat::FieldTypeMonthStandAlone:
91        m_hasMonth = true;
92        break;
93    case DateTimeFormat::FieldTypeWeekOfYear:
94        m_hasWeek = true;
95        break;
96    case DateTimeFormat::FieldTypeDayOfMonth:
97        m_hasDay = true;
98        break;
99    case DateTimeFormat::FieldTypePeriod:
100        m_hasAMPM = true;
101        break;
102    case DateTimeFormat::FieldTypeHour11: // Fallthrough.
103    case DateTimeFormat::FieldTypeHour12:
104        m_hasHour = true;
105        break;
106    case DateTimeFormat::FieldTypeHour23: // Fallthrough.
107    case DateTimeFormat::FieldTypeHour24:
108        m_hasHour = true;
109        m_hasAMPM = true;
110        break;
111    case DateTimeFormat::FieldTypeMinute:
112        m_hasMinute = true;
113        break;
114    case DateTimeFormat::FieldTypeSecond:
115        m_hasSecond = true;
116        break;
117    default:
118        break;
119    }
120}
121
122bool DateTimeFormatValidator::validateFormat(const String& format, const BaseMultipleFieldsDateAndTimeInputType& inputType)
123{
124    if (!DateTimeFormat::parse(format, *this))
125        return false;
126    return inputType.isValidFormat(m_hasYear, m_hasMonth, m_hasWeek, m_hasDay, m_hasAMPM, m_hasHour, m_hasMinute, m_hasSecond);
127}
128
129DateTimeEditElement* BaseMultipleFieldsDateAndTimeInputType::dateTimeEditElement() const
130{
131    return toDateTimeEditElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::dateTimeEdit()));
132}
133
134SpinButtonElement* BaseMultipleFieldsDateAndTimeInputType::spinButtonElement() const
135{
136    return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton()));
137}
138
139ClearButtonElement* BaseMultipleFieldsDateAndTimeInputType::clearButtonElement() const
140{
141    return toClearButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::clearButton()));
142}
143
144PickerIndicatorElement* BaseMultipleFieldsDateAndTimeInputType::pickerIndicatorElement() const
145{
146    return toPickerIndicatorElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::pickerIndicator()));
147}
148
149inline bool BaseMultipleFieldsDateAndTimeInputType::containsFocusedShadowElement() const
150{
151    return element().userAgentShadowRoot()->contains(element().document().focusedElement());
152}
153
154void BaseMultipleFieldsDateAndTimeInputType::didBlurFromControl()
155{
156    // We don't need to call blur(). This function is called when control
157    // lost focus.
158
159    if (containsFocusedShadowElement())
160        return;
161    RefPtr<HTMLInputElement> protector(element());
162    // Remove focus ring by CSS "focus" pseudo class.
163    element().setFocus(false);
164}
165
166void BaseMultipleFieldsDateAndTimeInputType::didFocusOnControl()
167{
168    // We don't need to call focus(). This function is called when control
169    // got focus.
170
171    if (!containsFocusedShadowElement())
172        return;
173    // Add focus ring by CSS "focus" pseudo class.
174    // FIXME: Setting the focus flag to non-focused element is too tricky.
175    element().setFocus(true);
176}
177
178void BaseMultipleFieldsDateAndTimeInputType::editControlValueChanged()
179{
180    RefPtr<HTMLInputElement> input(element());
181    String oldValue = input->value();
182    String newValue = sanitizeValue(dateTimeEditElement()->value());
183    // Even if oldValue is null and newValue is "", we should assume they are same.
184    if ((oldValue.isEmpty() && newValue.isEmpty()) || oldValue == newValue) {
185        input->setNeedsValidityCheck();
186    } else {
187        input->setValueInternal(newValue, DispatchNoEvent);
188        input->setNeedsStyleRecalc();
189        input->dispatchFormControlInputEvent();
190        input->dispatchFormControlChangeEvent();
191    }
192    input->notifyFormStateChanged();
193    input->updateClearButtonVisibility();
194}
195
196bool BaseMultipleFieldsDateAndTimeInputType::hasCustomFocusLogic() const
197{
198    return false;
199}
200
201bool BaseMultipleFieldsDateAndTimeInputType::isEditControlOwnerDisabled() const
202{
203    return element().isDisabledFormControl();
204}
205
206bool BaseMultipleFieldsDateAndTimeInputType::isEditControlOwnerReadOnly() const
207{
208    return element().isReadOnly();
209}
210
211void BaseMultipleFieldsDateAndTimeInputType::focusAndSelectSpinButtonOwner()
212{
213    if (DateTimeEditElement* edit = dateTimeEditElement())
214        edit->focusIfNoFocus();
215}
216
217bool BaseMultipleFieldsDateAndTimeInputType::shouldSpinButtonRespondToMouseEvents()
218{
219    return !element().isDisabledOrReadOnly();
220}
221
222bool BaseMultipleFieldsDateAndTimeInputType::shouldSpinButtonRespondToWheelEvents()
223{
224    if (!shouldSpinButtonRespondToMouseEvents())
225        return false;
226    if (DateTimeEditElement* edit = dateTimeEditElement())
227        return edit->hasFocusedField();
228    return false;
229}
230
231void BaseMultipleFieldsDateAndTimeInputType::spinButtonStepDown()
232{
233    if (DateTimeEditElement* edit = dateTimeEditElement())
234        edit->stepDown();
235}
236
237void BaseMultipleFieldsDateAndTimeInputType::spinButtonStepUp()
238{
239    if (DateTimeEditElement* edit = dateTimeEditElement())
240        edit->stepUp();
241}
242
243bool BaseMultipleFieldsDateAndTimeInputType::isPickerIndicatorOwnerDisabledOrReadOnly() const
244{
245    return element().isDisabledOrReadOnly();
246}
247
248void BaseMultipleFieldsDateAndTimeInputType::pickerIndicatorChooseValue(const String& value)
249{
250    if (element().isValidValue(value)) {
251        element().setValue(value, DispatchInputAndChangeEvent);
252        return;
253    }
254
255    DateTimeEditElement* edit = this->dateTimeEditElement();
256    if (!edit)
257        return;
258    DateComponents date;
259    unsigned end;
260    if (date.parseDate(value, 0, end) && end == value.length())
261        edit->setOnlyYearMonthDay(date);
262}
263
264void BaseMultipleFieldsDateAndTimeInputType::pickerIndicatorChooseValue(double value)
265{
266    ASSERT(std::isfinite(value) || std::isnan(value));
267    if (std::isnan(value))
268        element().setValue(emptyString(), DispatchInputAndChangeEvent);
269    else
270        element().setValueAsNumber(value, ASSERT_NO_EXCEPTION, DispatchInputAndChangeEvent);
271}
272
273bool BaseMultipleFieldsDateAndTimeInputType::setupDateTimeChooserParameters(DateTimeChooserParameters& parameters)
274{
275    return element().setupDateTimeChooserParameters(parameters);
276}
277
278BaseMultipleFieldsDateAndTimeInputType::BaseMultipleFieldsDateAndTimeInputType(HTMLInputElement& element)
279    : BaseDateAndTimeInputType(element)
280    , m_isDestroyingShadowSubtree(false)
281    , m_pickerIndicatorIsVisible(false)
282    , m_pickerIndicatorIsAlwaysVisible(false)
283{
284}
285
286BaseMultipleFieldsDateAndTimeInputType::~BaseMultipleFieldsDateAndTimeInputType()
287{
288    if (SpinButtonElement* element = spinButtonElement())
289        element->removeSpinButtonOwner();
290    if (ClearButtonElement* element = clearButtonElement())
291        element->removeClearButtonOwner();
292    if (DateTimeEditElement* element = dateTimeEditElement())
293        element->removeEditControlOwner();
294    if (PickerIndicatorElement* element = pickerIndicatorElement())
295        element->removePickerIndicatorOwner();
296}
297
298String BaseMultipleFieldsDateAndTimeInputType::badInputText() const
299{
300    return locale().queryString(blink::WebLocalizedString::ValidationBadInputForDateTime);
301}
302
303void BaseMultipleFieldsDateAndTimeInputType::blur()
304{
305    if (DateTimeEditElement* edit = dateTimeEditElement())
306        edit->blurByOwner();
307}
308
309PassRefPtr<RenderStyle> BaseMultipleFieldsDateAndTimeInputType::customStyleForRenderer(PassRefPtr<RenderStyle> originalStyle)
310{
311    EDisplay originalDisplay = originalStyle->display();
312    EDisplay newDisplay = originalDisplay;
313    if (originalDisplay == INLINE || originalDisplay == INLINE_BLOCK)
314        newDisplay = INLINE_FLEX;
315    else if (originalDisplay == BLOCK)
316        newDisplay = FLEX;
317    TextDirection contentDirection = element().locale().isRTL() ? RTL : LTR;
318    if (originalStyle->direction() == contentDirection && originalDisplay == newDisplay)
319        return originalStyle;
320
321    RefPtr<RenderStyle> style = RenderStyle::clone(originalStyle.get());
322    style->setDirection(contentDirection);
323    style->setDisplay(newDisplay);
324    style->setUnique();
325    return style.release();
326}
327
328void BaseMultipleFieldsDateAndTimeInputType::createShadowSubtree()
329{
330    ASSERT(element().shadow());
331
332    // Element must not have a renderer here, because if it did
333    // DateTimeEditElement::customStyleForRenderer() is called in appendChild()
334    // before the field wrapper element is created.
335    // FIXME: This code should not depend on such craziness.
336    ASSERT(!element().renderer());
337
338    Document& document = element().document();
339    ContainerNode* container = element().userAgentShadowRoot();
340
341    container->appendChild(DateTimeEditElement::create(document, *this));
342    updateView();
343    container->appendChild(ClearButtonElement::create(document, *this));
344    container->appendChild(SpinButtonElement::create(document, *this));
345
346    bool shouldAddPickerIndicator = false;
347    if (InputType::themeSupportsDataListUI(this))
348        shouldAddPickerIndicator = true;
349    if (RenderTheme::theme().supportsCalendarPicker(formControlType())) {
350        shouldAddPickerIndicator = true;
351        m_pickerIndicatorIsAlwaysVisible = true;
352    }
353    if (shouldAddPickerIndicator) {
354        container->appendChild(PickerIndicatorElement::create(document, *this));
355        m_pickerIndicatorIsVisible = true;
356        updatePickerIndicatorVisibility();
357    }
358}
359
360void BaseMultipleFieldsDateAndTimeInputType::destroyShadowSubtree()
361{
362    ASSERT(!m_isDestroyingShadowSubtree);
363    m_isDestroyingShadowSubtree = true;
364    if (SpinButtonElement* element = spinButtonElement())
365        element->removeSpinButtonOwner();
366    if (ClearButtonElement* element = clearButtonElement())
367        element->removeClearButtonOwner();
368    if (DateTimeEditElement* element = dateTimeEditElement())
369        element->removeEditControlOwner();
370    if (PickerIndicatorElement* element = pickerIndicatorElement())
371        element->removePickerIndicatorOwner();
372
373    // If a field element has focus, set focus back to the <input> itself before
374    // deleting the field. This prevents unnecessary focusout/blur events.
375    if (containsFocusedShadowElement())
376        element().focus();
377
378    BaseDateAndTimeInputType::destroyShadowSubtree();
379    m_isDestroyingShadowSubtree = false;
380}
381
382void BaseMultipleFieldsDateAndTimeInputType::handleFocusEvent(Element* oldFocusedElement, FocusDirection direction)
383{
384    DateTimeEditElement* edit = dateTimeEditElement();
385    if (!edit || m_isDestroyingShadowSubtree)
386        return;
387    if (direction == FocusDirectionBackward) {
388        if (element().document().page())
389            element().document().page()->focusController().advanceFocus(direction);
390    } else if (direction == FocusDirectionNone || direction == FocusDirectionMouse || direction == FocusDirectionPage) {
391        edit->focusByOwner(oldFocusedElement);
392    } else {
393        edit->focusByOwner();
394    }
395}
396
397void BaseMultipleFieldsDateAndTimeInputType::forwardEvent(Event* event)
398{
399    if (SpinButtonElement* element = spinButtonElement()) {
400        element->forwardEvent(event);
401        if (event->defaultHandled())
402            return;
403    }
404
405    if (DateTimeEditElement* edit = dateTimeEditElement())
406        edit->defaultEventHandler(event);
407}
408
409void BaseMultipleFieldsDateAndTimeInputType::disabledAttributeChanged()
410{
411    spinButtonElement()->releaseCapture();
412    clearButtonElement()->releaseCapture();
413    if (DateTimeEditElement* edit = dateTimeEditElement())
414        edit->disabledStateChanged();
415}
416
417void BaseMultipleFieldsDateAndTimeInputType::requiredAttributeChanged()
418{
419    clearButtonElement()->releaseCapture();
420    updateClearButtonVisibility();
421}
422
423void BaseMultipleFieldsDateAndTimeInputType::handleKeydownEvent(KeyboardEvent* event)
424{
425    if (m_pickerIndicatorIsVisible
426        && ((event->keyIdentifier() == "Down" && event->getModifierState("Alt")) || (RenderTheme::theme().shouldOpenPickerWithF4Key() && event->keyIdentifier() == "F4"))) {
427        if (PickerIndicatorElement* element = pickerIndicatorElement())
428            element->openPopup();
429        event->setDefaultHandled();
430    } else {
431        forwardEvent(event);
432    }
433}
434
435bool BaseMultipleFieldsDateAndTimeInputType::hasBadInput() const
436{
437    DateTimeEditElement* edit = dateTimeEditElement();
438    return element().value().isEmpty() && edit && edit->anyEditableFieldsHaveValues();
439}
440
441AtomicString BaseMultipleFieldsDateAndTimeInputType::localeIdentifier() const
442{
443    return element().computeInheritedLanguage();
444}
445
446void BaseMultipleFieldsDateAndTimeInputType::minOrMaxAttributeChanged()
447{
448    updateView();
449}
450
451void BaseMultipleFieldsDateAndTimeInputType::readonlyAttributeChanged()
452{
453    spinButtonElement()->releaseCapture();
454    clearButtonElement()->releaseCapture();
455    if (DateTimeEditElement* edit = dateTimeEditElement())
456        edit->readOnlyStateChanged();
457}
458
459void BaseMultipleFieldsDateAndTimeInputType::restoreFormControlState(const FormControlState& state)
460{
461    DateTimeEditElement* edit = dateTimeEditElement();
462    if (!edit)
463        return;
464    DateTimeFieldsState dateTimeFieldsState = DateTimeFieldsState::restoreFormControlState(state);
465    edit->setValueAsDateTimeFieldsState(dateTimeFieldsState);
466    element().setValueInternal(sanitizeValue(edit->value()), DispatchNoEvent);
467    updateClearButtonVisibility();
468}
469
470FormControlState BaseMultipleFieldsDateAndTimeInputType::saveFormControlState() const
471{
472    if (DateTimeEditElement* edit = dateTimeEditElement())
473        return edit->valueAsDateTimeFieldsState().saveFormControlState();
474    return FormControlState();
475}
476
477void BaseMultipleFieldsDateAndTimeInputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior)
478{
479    InputType::setValue(sanitizedValue, valueChanged, eventBehavior);
480    DateTimeEditElement* edit = dateTimeEditElement();
481    if (valueChanged || (sanitizedValue.isEmpty() && edit && edit->anyEditableFieldsHaveValues())) {
482        updateView();
483        element().setNeedsValidityCheck();
484    }
485}
486
487bool BaseMultipleFieldsDateAndTimeInputType::shouldUseInputMethod() const
488{
489    return false;
490}
491
492void BaseMultipleFieldsDateAndTimeInputType::stepAttributeChanged()
493{
494    updateView();
495}
496
497void BaseMultipleFieldsDateAndTimeInputType::updateView()
498{
499    DateTimeEditElement* edit = dateTimeEditElement();
500    if (!edit)
501        return;
502
503    DateTimeEditElement::LayoutParameters layoutParameters(element().locale(), createStepRange(AnyIsDefaultStep));
504
505    DateComponents date;
506    const bool hasValue = parseToDateComponents(element().value(), &date);
507    if (!hasValue)
508        setMillisecondToDateComponents(layoutParameters.stepRange.minimum().toDouble(), &date);
509
510    setupLayoutParameters(layoutParameters, date);
511
512    const AtomicString pattern = edit->fastGetAttribute(HTMLNames::patternAttr);
513    if (!pattern.isEmpty())
514        layoutParameters.dateTimeFormat = pattern;
515
516    if (!DateTimeFormatValidator().validateFormat(layoutParameters.dateTimeFormat, *this))
517        layoutParameters.dateTimeFormat = layoutParameters.fallbackDateTimeFormat;
518
519    if (hasValue)
520        edit->setValueAsDate(layoutParameters, date);
521    else
522        edit->setEmptyValue(layoutParameters, date);
523    updateClearButtonVisibility();
524}
525
526void BaseMultipleFieldsDateAndTimeInputType::valueAttributeChanged()
527{
528    if (!element().hasDirtyValue())
529        updateView();
530}
531
532void BaseMultipleFieldsDateAndTimeInputType::listAttributeTargetChanged()
533{
534    updatePickerIndicatorVisibility();
535}
536
537void BaseMultipleFieldsDateAndTimeInputType::updatePickerIndicatorVisibility()
538{
539    if (m_pickerIndicatorIsAlwaysVisible) {
540        showPickerIndicator();
541        return;
542    }
543    if (RuntimeEnabledFeatures::dataListElementEnabled()) {
544        if (element().hasValidDataListOptions())
545            showPickerIndicator();
546        else
547            hidePickerIndicator();
548    }
549}
550
551void BaseMultipleFieldsDateAndTimeInputType::hidePickerIndicator()
552{
553    if (!m_pickerIndicatorIsVisible)
554        return;
555    m_pickerIndicatorIsVisible = false;
556    ASSERT(pickerIndicatorElement());
557    pickerIndicatorElement()->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
558}
559
560void BaseMultipleFieldsDateAndTimeInputType::showPickerIndicator()
561{
562    if (m_pickerIndicatorIsVisible)
563        return;
564    m_pickerIndicatorIsVisible = true;
565    ASSERT(pickerIndicatorElement());
566    pickerIndicatorElement()->removeInlineStyleProperty(CSSPropertyDisplay);
567}
568
569bool BaseMultipleFieldsDateAndTimeInputType::shouldHaveSecondField(const DateComponents& date) const
570{
571    StepRange stepRange = createStepRange(AnyIsDefaultStep);
572    return date.second() || date.millisecond()
573        || !stepRange.minimum().remainder(static_cast<int>(msPerMinute)).isZero()
574        || !stepRange.step().remainder(static_cast<int>(msPerMinute)).isZero();
575}
576
577void BaseMultipleFieldsDateAndTimeInputType::focusAndSelectClearButtonOwner()
578{
579    element().focus();
580}
581
582bool BaseMultipleFieldsDateAndTimeInputType::shouldClearButtonRespondToMouseEvents()
583{
584    return !element().isDisabledOrReadOnly() && !element().isRequired();
585}
586
587void BaseMultipleFieldsDateAndTimeInputType::clearValue()
588{
589    RefPtr<HTMLInputElement> input(element());
590    input->setValue("", DispatchInputAndChangeEvent);
591    input->updateClearButtonVisibility();
592}
593
594void BaseMultipleFieldsDateAndTimeInputType::updateClearButtonVisibility()
595{
596    ClearButtonElement* clearButton = clearButtonElement();
597    if (!clearButton)
598        return;
599
600    if (element().isRequired() || !dateTimeEditElement()->anyEditableFieldsHaveValues()) {
601        clearButton->setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER);
602        clearButton->setInlineStyleProperty(CSSPropertyPointerEvents, CSSValueNone);
603    } else {
604        clearButton->removeInlineStyleProperty(CSSPropertyOpacity);
605        clearButton->removeInlineStyleProperty(CSSPropertyPointerEvents);
606    }
607}
608
609} // namespace WebCore
610
611#endif
612