1/*
2 * Copyright (C) 2010, 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#include "ValidationMessage.h"
33
34#include "CSSPropertyNames.h"
35#include "CSSValueKeywords.h"
36#include "ExceptionCodePlaceholder.h"
37#include "HTMLBRElement.h"
38#include "HTMLDivElement.h"
39#include "HTMLFormControlElement.h"
40#include "HTMLNames.h"
41#include "Page.h"
42#include "RenderBlock.h"
43#include "RenderObject.h"
44#include "Settings.h"
45#include "ShadowRoot.h"
46#include "StyleResolver.h"
47#include "Text.h"
48#include "ValidationMessageClient.h"
49
50namespace WebCore {
51
52using namespace HTMLNames;
53
54ValidationMessage::ValidationMessage(HTMLFormControlElement* element)
55    : m_element(element)
56{
57    ASSERT(m_element);
58}
59
60ValidationMessage::~ValidationMessage()
61{
62    if (ValidationMessageClient* client = validationMessageClient()) {
63        client->hideValidationMessage(*m_element);
64        return;
65    }
66
67    deleteBubbleTree();
68}
69
70ValidationMessageClient* ValidationMessage::validationMessageClient() const
71{
72    if (Page* page = m_element->document().page())
73        return page->validationMessageClient();
74    return 0;
75}
76
77void ValidationMessage::updateValidationMessage(const String& message)
78{
79    String updatedMessage = message;
80    if (!validationMessageClient()) {
81        // HTML5 specification doesn't ask UA to show the title attribute value
82        // with the validationMessage. However, this behavior is same as Opera
83        // and the specification describes such behavior as an example.
84        if (!updatedMessage.isEmpty()) {
85            const AtomicString& title = m_element->fastGetAttribute(titleAttr);
86            if (!title.isEmpty())
87                updatedMessage = updatedMessage + '\n' + title;
88        }
89    }
90
91    if (updatedMessage.isEmpty()) {
92        requestToHideMessage();
93        return;
94    }
95    setMessage(updatedMessage);
96}
97
98void ValidationMessage::setMessage(const String& message)
99{
100    if (ValidationMessageClient* client = validationMessageClient()) {
101        client->showValidationMessage(*m_element, message);
102        return;
103    }
104
105    // Don't modify the DOM tree in this context.
106    // If so, an assertion in Element::isFocusable() fails.
107    ASSERT(!message.isEmpty());
108    m_message = message;
109    if (!m_bubble)
110        m_timer = std::make_unique<Timer<ValidationMessage>>(this, &ValidationMessage::buildBubbleTree);
111    else
112        m_timer = std::make_unique<Timer<ValidationMessage>>(this, &ValidationMessage::setMessageDOMAndStartTimer);
113    m_timer->startOneShot(0);
114}
115
116void ValidationMessage::setMessageDOMAndStartTimer(Timer<ValidationMessage>*)
117{
118    ASSERT(!validationMessageClient());
119    ASSERT(m_messageHeading);
120    ASSERT(m_messageBody);
121    m_messageHeading->removeChildren();
122    m_messageBody->removeChildren();
123    Vector<String> lines;
124    m_message.split('\n', lines);
125    Document& document = m_messageHeading->document();
126    for (unsigned i = 0; i < lines.size(); ++i) {
127        if (i) {
128            m_messageBody->appendChild(Text::create(document, lines[i]), ASSERT_NO_EXCEPTION);
129            if (i < lines.size() - 1)
130                m_messageBody->appendChild(HTMLBRElement::create(document), ASSERT_NO_EXCEPTION);
131        } else
132            m_messageHeading->setInnerText(lines[i], ASSERT_NO_EXCEPTION);
133    }
134
135    int magnification = document.page() ? document.page()->settings().validationMessageTimerMagnification() : -1;
136    if (magnification <= 0)
137        m_timer = nullptr;
138    else {
139        m_timer = std::make_unique<Timer<ValidationMessage>>(this, &ValidationMessage::deleteBubbleTree);
140        m_timer->startOneShot(std::max(5.0, static_cast<double>(m_message.length()) * magnification / 1000));
141    }
142}
143
144static void adjustBubblePosition(const LayoutRect& hostRect, HTMLElement* bubble)
145{
146    ASSERT(bubble);
147    if (hostRect.isEmpty())
148        return;
149    double hostX = hostRect.x();
150    double hostY = hostRect.y();
151    if (RenderObject* renderer = bubble->renderer()) {
152        if (RenderBox* container = renderer->containingBlock()) {
153            FloatPoint containerLocation = container->localToAbsolute();
154            hostX -= containerLocation.x() + container->borderLeft();
155            hostY -= containerLocation.y() + container->borderTop();
156        }
157    }
158
159    bubble->setInlineStyleProperty(CSSPropertyTop, hostY + hostRect.height(), CSSPrimitiveValue::CSS_PX);
160    // The 'left' value of ::-webkit-validation-bubble-arrow.
161    const int bubbleArrowTopOffset = 32;
162    double bubbleX = hostX;
163    if (hostRect.width() / 2 < bubbleArrowTopOffset)
164        bubbleX = std::max(hostX + hostRect.width() / 2 - bubbleArrowTopOffset, 0.0);
165    bubble->setInlineStyleProperty(CSSPropertyLeft, bubbleX, CSSPrimitiveValue::CSS_PX);
166}
167
168void ValidationMessage::buildBubbleTree(Timer<ValidationMessage>*)
169{
170    ASSERT(!validationMessageClient());
171    ShadowRoot& shadowRoot = m_element->ensureUserAgentShadowRoot();
172
173    Document& document = m_element->document();
174    m_bubble = HTMLDivElement::create(document);
175    m_bubble->setPseudo(AtomicString("-webkit-validation-bubble", AtomicString::ConstructFromLiteral));
176    // Need to force position:absolute because RenderMenuList doesn't assume it
177    // contains non-absolute or non-fixed renderers as children.
178    m_bubble->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
179    shadowRoot.appendChild(m_bubble.get(), ASSERT_NO_EXCEPTION);
180    document.updateLayout();
181    adjustBubblePosition(m_element->boundingBox(), m_bubble.get());
182
183    RefPtr<HTMLDivElement> clipper = HTMLDivElement::create(document);
184    clipper->setPseudo(AtomicString("-webkit-validation-bubble-arrow-clipper", AtomicString::ConstructFromLiteral));
185    RefPtr<HTMLDivElement> bubbleArrow = HTMLDivElement::create(document);
186    bubbleArrow->setPseudo(AtomicString("-webkit-validation-bubble-arrow", AtomicString::ConstructFromLiteral));
187    clipper->appendChild(bubbleArrow.release(), ASSERT_NO_EXCEPTION);
188    m_bubble->appendChild(clipper.release(), ASSERT_NO_EXCEPTION);
189
190    RefPtr<HTMLElement> message = HTMLDivElement::create(document);
191    message->setPseudo(AtomicString("-webkit-validation-bubble-message", AtomicString::ConstructFromLiteral));
192    RefPtr<HTMLElement> icon = HTMLDivElement::create(document);
193    icon->setPseudo(AtomicString("-webkit-validation-bubble-icon", AtomicString::ConstructFromLiteral));
194    message->appendChild(icon.release(), ASSERT_NO_EXCEPTION);
195    RefPtr<HTMLElement> textBlock = HTMLDivElement::create(document);
196    textBlock->setPseudo(AtomicString("-webkit-validation-bubble-text-block", AtomicString::ConstructFromLiteral));
197    m_messageHeading = HTMLDivElement::create(document);
198    m_messageHeading->setPseudo(AtomicString("-webkit-validation-bubble-heading", AtomicString::ConstructFromLiteral));
199    textBlock->appendChild(m_messageHeading, ASSERT_NO_EXCEPTION);
200    m_messageBody = HTMLDivElement::create(document);
201    m_messageBody->setPseudo(AtomicString("-webkit-validation-bubble-body", AtomicString::ConstructFromLiteral));
202    textBlock->appendChild(m_messageBody, ASSERT_NO_EXCEPTION);
203    message->appendChild(textBlock.release(), ASSERT_NO_EXCEPTION);
204    m_bubble->appendChild(message.release(), ASSERT_NO_EXCEPTION);
205
206    setMessageDOMAndStartTimer();
207
208    // FIXME: Use transition to show the bubble.
209}
210
211void ValidationMessage::requestToHideMessage()
212{
213    if (ValidationMessageClient* client = validationMessageClient()) {
214        client->hideValidationMessage(*m_element);
215        return;
216    }
217
218    // We must not modify the DOM tree in this context by the same reason as setMessage().
219    m_timer = std::make_unique<Timer<ValidationMessage>>(this, &ValidationMessage::deleteBubbleTree);
220    m_timer->startOneShot(0);
221}
222
223bool ValidationMessage::shadowTreeContains(const Node& node) const
224{
225    if (validationMessageClient() || !m_bubble)
226        return false;
227    return &m_bubble->treeScope() == &node.treeScope();
228}
229
230void ValidationMessage::deleteBubbleTree(Timer<ValidationMessage>*)
231{
232    ASSERT(!validationMessageClient());
233    if (m_bubble) {
234        m_messageHeading = 0;
235        m_messageBody = 0;
236        m_element->userAgentShadowRoot()->removeChild(m_bubble.get(), ASSERT_NO_EXCEPTION);
237        m_bubble = 0;
238    }
239    m_message = String();
240}
241
242bool ValidationMessage::isVisible() const
243{
244    if (ValidationMessageClient* client = validationMessageClient())
245        return client->isValidationMessageVisible(*m_element);
246    return !m_message.isEmpty();
247}
248
249} // namespace WebCore
250