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