1/*
2 * Copyright (C) 2012 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/shadow/PickerIndicatorElement.h"
34
35#include "core/events/Event.h"
36#include "core/events/KeyboardEvent.h"
37#include "core/frame/Settings.h"
38#include "core/html/shadow/ShadowElementNames.h"
39#include "core/page/Chrome.h"
40#include "core/page/Page.h"
41#include "core/rendering/RenderDetailsMarker.h"
42#include "platform/LayoutTestSupport.h"
43#include "wtf/TemporaryChange.h"
44
45using namespace WTF::Unicode;
46
47namespace blink {
48
49using namespace HTMLNames;
50
51inline PickerIndicatorElement::PickerIndicatorElement(Document& document, PickerIndicatorOwner& pickerIndicatorOwner)
52    : HTMLDivElement(document)
53    , m_pickerIndicatorOwner(&pickerIndicatorOwner)
54    , m_isInOpenPopup(false)
55{
56}
57
58PassRefPtrWillBeRawPtr<PickerIndicatorElement> PickerIndicatorElement::create(Document& document, PickerIndicatorOwner& pickerIndicatorOwner)
59{
60    RefPtrWillBeRawPtr<PickerIndicatorElement> element = adoptRefWillBeNoop(new PickerIndicatorElement(document, pickerIndicatorOwner));
61    element->setShadowPseudoId(AtomicString("-webkit-calendar-picker-indicator", AtomicString::ConstructFromLiteral));
62    element->setAttribute(idAttr, ShadowElementNames::pickerIndicator());
63    return element.release();
64}
65
66PickerIndicatorElement::~PickerIndicatorElement()
67{
68    closePopup();
69    ASSERT(!m_chooser);
70}
71
72RenderObject* PickerIndicatorElement::createRenderer(RenderStyle*)
73{
74    return new RenderDetailsMarker(this);
75}
76
77void PickerIndicatorElement::defaultEventHandler(Event* event)
78{
79    if (!renderer())
80        return;
81    if (!m_pickerIndicatorOwner || m_pickerIndicatorOwner->isPickerIndicatorOwnerDisabledOrReadOnly())
82        return;
83
84    if (event->type() == EventTypeNames::click) {
85        openPopup();
86        event->setDefaultHandled();
87    } else if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
88        int charCode = toKeyboardEvent(event)->charCode();
89        if (charCode == ' ' || charCode == '\r') {
90            openPopup();
91            event->setDefaultHandled();
92        }
93    }
94
95    if (!event->defaultHandled())
96        HTMLDivElement::defaultEventHandler(event);
97}
98
99bool PickerIndicatorElement::willRespondToMouseClickEvents()
100{
101    if (renderer() && m_pickerIndicatorOwner && !m_pickerIndicatorOwner->isPickerIndicatorOwnerDisabledOrReadOnly())
102        return true;
103
104    return HTMLDivElement::willRespondToMouseClickEvents();
105}
106
107void PickerIndicatorElement::didChooseValue(const String& value)
108{
109    if (!m_pickerIndicatorOwner)
110        return;
111    m_pickerIndicatorOwner->pickerIndicatorChooseValue(value);
112}
113
114void PickerIndicatorElement::didChooseValue(double value)
115{
116    if (m_pickerIndicatorOwner)
117        m_pickerIndicatorOwner->pickerIndicatorChooseValue(value);
118}
119
120void PickerIndicatorElement::didEndChooser()
121{
122    m_chooser.clear();
123}
124
125void PickerIndicatorElement::openPopup()
126{
127    // The m_isInOpenPopup flag is unnecessary in production.
128    // MockPagePopupDriver allows to execute JavaScript code in
129    // DateTimeChooserImpl constructor. It might create another DateTimeChooser.
130    if (m_isInOpenPopup)
131        return;
132    TemporaryChange<bool> reentrancyProtector(m_isInOpenPopup, true);
133    if (m_chooser)
134        return;
135    if (!document().page())
136        return;
137    if (!m_pickerIndicatorOwner)
138        return;
139    DateTimeChooserParameters parameters;
140    if (!m_pickerIndicatorOwner->setupDateTimeChooserParameters(parameters))
141        return;
142    m_chooser = document().page()->chrome().openDateTimeChooser(this, parameters);
143}
144
145Element& PickerIndicatorElement::ownerElement() const
146{
147    ASSERT(m_pickerIndicatorOwner);
148    return m_pickerIndicatorOwner->pickerOwnerElement();
149}
150
151void PickerIndicatorElement::closePopup()
152{
153    if (!m_chooser)
154        return;
155    m_chooser->endChooser();
156}
157
158void PickerIndicatorElement::detach(const AttachContext& context)
159{
160    closePopup();
161    HTMLDivElement::detach(context);
162}
163
164AXObject* PickerIndicatorElement::popupRootAXObject() const
165{
166    return m_chooser ? m_chooser->rootAXObject() : 0;
167}
168
169bool PickerIndicatorElement::isPickerIndicatorElement() const
170{
171    return true;
172}
173
174Node::InsertionNotificationRequest PickerIndicatorElement::insertedInto(ContainerNode* insertionPoint)
175{
176    HTMLDivElement::insertedInto(insertionPoint);
177    return InsertionShouldCallDidNotifySubtreeInsertions;
178}
179
180void PickerIndicatorElement::didNotifySubtreeInsertionsToDocument()
181{
182    if (!document().settings() || !document().settings()->accessibilityEnabled())
183        return;
184    // Don't make this focusable if we are in layout tests in order to avoid to
185    // break existing tests.
186    // FIXME: We should have a way to disable accessibility in layout tests.
187    if (LayoutTestSupport::isRunningLayoutTest())
188        return;
189    setAttribute(tabindexAttr, "0");
190    setAttribute(aria_haspopupAttr, "true");
191    setAttribute(roleAttr, "button");
192}
193
194void PickerIndicatorElement::trace(Visitor* visitor)
195{
196    visitor->trace(m_pickerIndicatorOwner);
197    HTMLDivElement::trace(visitor);
198}
199
200}
201
202#endif
203