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