[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebCore / html / ValidationMessage.cpp
1 /*
2  * Copyright (C) 2010, 2012 Google Inc. All rights reserved.
3  * Copyright (C) 2019 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "ValidationMessage.h"
34
35 #include "CSSPropertyNames.h"
36 #include "CSSValueKeywords.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
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 ValidationMessage::ValidationMessage(HTMLFormControlElement* element)
55     : m_element(element)
56 {
57     ASSERT(m_element);
58 }
59
60 ValidationMessage::~ValidationMessage()
61 {
62     if (ValidationMessageClient* client = validationMessageClient()) {
63         client->hideValidationMessage(*m_element);
64         return;
65     }
66
67     deleteBubbleTree();
68 }
69
70 ValidationMessageClient* ValidationMessage::validationMessageClient() const
71 {
72     if (Page* page = m_element->document().page())
73         return page->validationMessageClient();
74     return 0;
75 }
76
77 void ValidationMessage::updateValidationMessage(const String& message)
78 {
79     // We want to hide the validation message as soon as the user starts
80     // typing, even if a constraint is still violated. Thefore, we hide the message instead
81     // of updating it if it is already visible.
82     if (isVisible()) {
83         requestToHideMessage();
84         return;
85     }
86
87     String updatedMessage = message;
88     if (!validationMessageClient()) {
89         // HTML5 specification doesn't ask UA to show the title attribute value
90         // with the validationMessage. However, this behavior is same as Opera
91         // and the specification describes such behavior as an example.
92         if (!updatedMessage.isEmpty()) {
93             const AtomString& title = m_element->attributeWithoutSynchronization(titleAttr);
94             if (!title.isEmpty())
95                 updatedMessage = updatedMessage + '\n' + title;
96         }
97     }
98
99     if (updatedMessage.isEmpty()) {
100         requestToHideMessage();
101         return;
102     }
103     setMessage(updatedMessage);
104 }
105
106 void ValidationMessage::setMessage(const String& message)
107 {
108     if (ValidationMessageClient* client = validationMessageClient()) {
109         client->showValidationMessage(*m_element, message);
110         return;
111     }
112
113     // Don't modify the DOM tree in this context.
114     // If so, an assertion in Element::isFocusable() fails.
115     ASSERT(!message.isEmpty());
116     m_message = message;
117     if (!m_bubble)
118         m_timer = makeUnique<Timer>(*this, &ValidationMessage::buildBubbleTree);
119     else
120         m_timer = makeUnique<Timer>(*this, &ValidationMessage::setMessageDOMAndStartTimer);
121     m_timer->startOneShot(0_s);
122 }
123
124 void ValidationMessage::setMessageDOMAndStartTimer()
125 {
126     ASSERT(!validationMessageClient());
127     ASSERT(m_messageHeading);
128     ASSERT(m_messageBody);
129     m_messageHeading->removeChildren();
130     m_messageBody->removeChildren();
131     Vector<String> lines = m_message.split('\n');
132     Document& document = m_messageHeading->document();
133     for (unsigned i = 0; i < lines.size(); ++i) {
134         if (i) {
135             m_messageBody->appendChild(Text::create(document, lines[i]));
136             if (i < lines.size() - 1)
137                 m_messageBody->appendChild(HTMLBRElement::create(document));
138         } else
139             m_messageHeading->setInnerText(lines[i]);
140     }
141
142     int magnification = document.page() ? document.page()->settings().validationMessageTimerMagnification() : -1;
143     if (magnification <= 0)
144         m_timer = nullptr;
145     else {
146         m_timer = makeUnique<Timer>(*this, &ValidationMessage::deleteBubbleTree);
147         m_timer->startOneShot(std::max(5_s, 1_ms * static_cast<double>(m_message.length()) * magnification));
148     }
149 }
150
151 static 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 = std::max(hostX + hostRect.width() / 2 - bubbleArrowTopOffset, 0.0);
172     bubble->setInlineStyleProperty(CSSPropertyLeft, bubbleX, CSSPrimitiveValue::CSS_PX);
173 }
174
175 void ValidationMessage::buildBubbleTree()
176 {
177     ASSERT(!validationMessageClient());
178
179     if (!m_element->renderer())
180         return;
181
182     ShadowRoot& shadowRoot = m_element->ensureUserAgentShadowRoot();
183
184     Document& document = m_element->document();
185     m_bubble = HTMLDivElement::create(document);
186     m_bubble->setPseudo(AtomString("-webkit-validation-bubble", AtomString::ConstructFromLiteral));
187     // Need to force position:absolute because RenderMenuList doesn't assume it
188     // contains non-absolute or non-fixed renderers as children.
189     m_bubble->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
190     shadowRoot.appendChild(*m_bubble);
191
192     auto weakElement = makeWeakPtr(*m_element);
193
194     document.updateLayout();
195
196     if (!weakElement || !m_element->renderer())
197         return;
198
199     adjustBubblePosition(m_element->renderer()->absoluteBoundingBoxRect(), m_bubble.get());
200
201     auto clipper = HTMLDivElement::create(document);
202     clipper->setPseudo(AtomString("-webkit-validation-bubble-arrow-clipper", AtomString::ConstructFromLiteral));
203     auto bubbleArrow = HTMLDivElement::create(document);
204     bubbleArrow->setPseudo(AtomString("-webkit-validation-bubble-arrow", AtomString::ConstructFromLiteral));
205     clipper->appendChild(bubbleArrow);
206     m_bubble->appendChild(clipper);
207
208     auto message = HTMLDivElement::create(document);
209     message->setPseudo(AtomString("-webkit-validation-bubble-message", AtomString::ConstructFromLiteral));
210     auto icon = HTMLDivElement::create(document);
211     icon->setPseudo(AtomString("-webkit-validation-bubble-icon", AtomString::ConstructFromLiteral));
212     message->appendChild(icon);
213     auto textBlock = HTMLDivElement::create(document);
214     textBlock->setPseudo(AtomString("-webkit-validation-bubble-text-block", AtomString::ConstructFromLiteral));
215     m_messageHeading = HTMLDivElement::create(document);
216     m_messageHeading->setPseudo(AtomString("-webkit-validation-bubble-heading", AtomString::ConstructFromLiteral));
217     textBlock->appendChild(*m_messageHeading);
218     m_messageBody = HTMLDivElement::create(document);
219     m_messageBody->setPseudo(AtomString("-webkit-validation-bubble-body", AtomString::ConstructFromLiteral));
220     textBlock->appendChild(*m_messageBody);
221     message->appendChild(textBlock);
222     m_bubble->appendChild(message);
223
224     setMessageDOMAndStartTimer();
225
226     // FIXME: Use transition to show the bubble.
227 }
228
229 void ValidationMessage::requestToHideMessage()
230 {
231     if (ValidationMessageClient* client = validationMessageClient()) {
232         client->hideValidationMessage(*m_element);
233         return;
234     }
235
236     // We must not modify the DOM tree in this context by the same reason as setMessage().
237     m_timer = makeUnique<Timer>(*this, &ValidationMessage::deleteBubbleTree);
238     m_timer->startOneShot(0_s);
239 }
240
241 bool ValidationMessage::shadowTreeContains(const Node& node) const
242 {
243     if (validationMessageClient() || !m_bubble)
244         return false;
245     return &m_bubble->treeScope() == &node.treeScope();
246 }
247
248 void ValidationMessage::deleteBubbleTree()
249 {
250     ASSERT(!validationMessageClient());
251     if (m_bubble) {
252         m_messageHeading = nullptr;
253         m_messageBody = nullptr;
254         m_element->userAgentShadowRoot()->removeChild(*m_bubble);
255         m_bubble = nullptr;
256     }
257     m_message = String();
258 }
259
260 bool ValidationMessage::isVisible() const
261 {
262     if (ValidationMessageClient* client = validationMessageClient())
263         return client->isValidationMessageVisible(*m_element);
264     return !m_message.isEmpty();
265 }
266
267 } // namespace WebCore