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 "HTMLBRElement.h"
37#include "HTMLDivElement.h"
38#include "HTMLFormControlElement.h"
39#include "HTMLNames.h"
40#include "Page.h"
41#include "RenderBlock.h"
42#include "RenderObject.h"
43#include "Settings.h"
44#include "ShadowRoot.h"
45#include "StyleResolver.h"
46#include "Text.h"
47#include "ValidationMessageClient.h"
48
49namespace WebCore {
50
51using namespace HTMLNames;
52
53ValidationMessage::ValidationMessage(HTMLFormControlElement* element)
54 : m_element(element)
55{
56 ASSERT(m_element);
57}
58
59ValidationMessage::~ValidationMessage()
60{
61 if (ValidationMessageClient* client = validationMessageClient()) {
62 client->hideValidationMessage(*m_element);
63 return;
64 }
65
66 deleteBubbleTree();
67}
68
69ValidationMessageClient* ValidationMessage::validationMessageClient() const
70{
71 if (Page* page = m_element->document().page())
72 return page->validationMessageClient();
73 return 0;
74}
75
76void ValidationMessage::updateValidationMessage(const String& message)
77{
78 // We want to hide the validation message as soon as the user starts
79 // typing, even if a constraint is still violated. Thefore, we hide the message instead
80 // of updating it if it is already visible.
81 if (isVisible()) {
82 requestToHideMessage();
83 return;
84 }
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 if (!updatedMessage.isEmpty()) {
92 const AtomicString& title = m_element->attributeWithoutSynchronization(titleAttr);
93 if (!title.isEmpty())
94 updatedMessage = updatedMessage + '\n' + 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 = std::make_unique<Timer>(*this, &ValidationMessage::buildBubbleTree);
118 else
119 m_timer = std::make_unique<Timer>(*this, &ValidationMessage::setMessageDOMAndStartTimer);
120 m_timer->startOneShot(0_s);
121}
122
123void ValidationMessage::setMessageDOMAndStartTimer()
124{
125 ASSERT(!validationMessageClient());
126 ASSERT(m_messageHeading);
127 ASSERT(m_messageBody);
128 m_messageHeading->removeChildren();
129 m_messageBody->removeChildren();
130 Vector<String> lines = m_message.split('\n');
131 Document& document = m_messageHeading->document();
132 for (unsigned i = 0; i < lines.size(); ++i) {
133 if (i) {
134 m_messageBody->appendChild(Text::create(document, lines[i]));
135 if (i < lines.size() - 1)
136 m_messageBody->appendChild(HTMLBRElement::create(document));
137 } else
138 m_messageHeading->setInnerText(lines[i]);
139 }
140
141 int magnification = document.page() ? document.page()->settings().validationMessageTimerMagnification() : -1;
142 if (magnification <= 0)
143 m_timer = nullptr;
144 else {
145 m_timer = std::make_unique<Timer>(*this, &ValidationMessage::deleteBubbleTree);
146 m_timer->startOneShot(std::max(5_s, 1_ms * static_cast<double>(m_message.length()) * magnification));
147 }
148}
149
150static void adjustBubblePosition(const LayoutRect& hostRect, HTMLElement* bubble)
151{
152 ASSERT(bubble);
153 if (hostRect.isEmpty())
154 return;
155 double hostX = hostRect.x();
156 double hostY = hostRect.y();
157 if (RenderObject* renderer = bubble->renderer()) {
158 if (RenderBox* container = renderer->containingBlock()) {
159 FloatPoint containerLocation = container->localToAbsolute();
160 hostX -= containerLocation.x() + container->borderLeft();
161 hostY -= containerLocation.y() + container->borderTop();
162 }
163 }
164
165 bubble->setInlineStyleProperty(CSSPropertyTop, hostY + hostRect.height(), CSSPrimitiveValue::CSS_PX);
166 // The 'left' value of ::-webkit-validation-bubble-arrow.
167 const int bubbleArrowTopOffset = 32;
168 double bubbleX = hostX;
169 if (hostRect.width() / 2 < bubbleArrowTopOffset)
170 bubbleX = std::max(hostX + hostRect.width() / 2 - bubbleArrowTopOffset, 0.0);
171 bubble->setInlineStyleProperty(CSSPropertyLeft, bubbleX, CSSPrimitiveValue::CSS_PX);
172}
173
174void ValidationMessage::buildBubbleTree()
175{
176 ASSERT(!validationMessageClient());
177
178 if (!m_element->renderer())
179 return;
180
181 ShadowRoot& shadowRoot = m_element->ensureUserAgentShadowRoot();
182
183 Document& document = m_element->document();
184 m_bubble = HTMLDivElement::create(document);
185 m_bubble->setPseudo(AtomicString("-webkit-validation-bubble", AtomicString::ConstructFromLiteral));
186 // Need to force position:absolute because RenderMenuList doesn't assume it
187 // contains non-absolute or non-fixed renderers as children.
188 m_bubble->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
189 shadowRoot.appendChild(*m_bubble);
190 document.updateLayout();
191 adjustBubblePosition(m_element->renderer()->absoluteBoundingBoxRect(), m_bubble.get());
192
193 auto clipper = HTMLDivElement::create(document);
194 clipper->setPseudo(AtomicString("-webkit-validation-bubble-arrow-clipper", AtomicString::ConstructFromLiteral));
195 auto bubbleArrow = HTMLDivElement::create(document);
196 bubbleArrow->setPseudo(AtomicString("-webkit-validation-bubble-arrow", AtomicString::ConstructFromLiteral));
197 clipper->appendChild(bubbleArrow);
198 m_bubble->appendChild(clipper);
199
200 auto message = HTMLDivElement::create(document);
201 message->setPseudo(AtomicString("-webkit-validation-bubble-message", AtomicString::ConstructFromLiteral));
202 auto icon = HTMLDivElement::create(document);
203 icon->setPseudo(AtomicString("-webkit-validation-bubble-icon", AtomicString::ConstructFromLiteral));
204 message->appendChild(icon);
205 auto textBlock = HTMLDivElement::create(document);
206 textBlock->setPseudo(AtomicString("-webkit-validation-bubble-text-block", AtomicString::ConstructFromLiteral));
207 m_messageHeading = HTMLDivElement::create(document);
208 m_messageHeading->setPseudo(AtomicString("-webkit-validation-bubble-heading", AtomicString::ConstructFromLiteral));
209 textBlock->appendChild(*m_messageHeading);
210 m_messageBody = HTMLDivElement::create(document);
211 m_messageBody->setPseudo(AtomicString("-webkit-validation-bubble-body", AtomicString::ConstructFromLiteral));
212 textBlock->appendChild(*m_messageBody);
213 message->appendChild(textBlock);
214 m_bubble->appendChild(message);
215
216 setMessageDOMAndStartTimer();
217
218 // FIXME: Use transition to show the bubble.
219}
220
221void ValidationMessage::requestToHideMessage()
222{
223 if (ValidationMessageClient* client = validationMessageClient()) {
224 client->hideValidationMessage(*m_element);
225 return;
226 }
227
228 // We must not modify the DOM tree in this context by the same reason as setMessage().
229 m_timer = std::make_unique<Timer>(*this, &ValidationMessage::deleteBubbleTree);
230 m_timer->startOneShot(0_s);
231}
232
233bool ValidationMessage::shadowTreeContains(const Node& node) const
234{
235 if (validationMessageClient() || !m_bubble)
236 return false;
237 return &m_bubble->treeScope() == &node.treeScope();
238}
239
240void ValidationMessage::deleteBubbleTree()
241{
242 ASSERT(!validationMessageClient());
243 if (m_bubble) {
244 m_messageHeading = nullptr;
245 m_messageBody = nullptr;
246 m_element->userAgentShadowRoot()->removeChild(*m_bubble);
247 m_bubble = nullptr;
248 }
249 m_message = String();
250}
251
252bool ValidationMessage::isVisible() const
253{
254 if (ValidationMessageClient* client = validationMessageClient())
255 return client->isValidationMessageVisible(*m_element);
256 return !m_message.isEmpty();
257}
258
259} // namespace WebCore
260