ValidationMessage.cpp [plain text]
#include "config.h"
#include "ValidationMessage.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "ExceptionCodePlaceholder.h"
#include "HTMLBRElement.h"
#include "HTMLDivElement.h"
#include "HTMLFormControlElement.h"
#include "HTMLNames.h"
#include "Page.h"
#include "RenderBlock.h"
#include "RenderObject.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "StyleResolver.h"
#include "Text.h"
#include "ValidationMessageClient.h"
namespace WebCore {
using namespace HTMLNames;
ValidationMessage::ValidationMessage(HTMLFormControlElement* element)
: m_element(element)
{
ASSERT(m_element);
}
ValidationMessage::~ValidationMessage()
{
if (ValidationMessageClient* client = validationMessageClient()) {
client->hideValidationMessage(*m_element);
return;
}
deleteBubbleTree();
}
ValidationMessageClient* ValidationMessage::validationMessageClient() const
{
if (Page* page = m_element->document().page())
return page->validationMessageClient();
return 0;
}
void ValidationMessage::updateValidationMessage(const String& message)
{
String updatedMessage = message;
if (!validationMessageClient()) {
if (!updatedMessage.isEmpty()) {
const AtomicString& title = m_element->fastGetAttribute(titleAttr);
if (!title.isEmpty())
updatedMessage = updatedMessage + '\n' + title;
}
}
if (updatedMessage.isEmpty()) {
requestToHideMessage();
return;
}
setMessage(updatedMessage);
}
void ValidationMessage::setMessage(const String& message)
{
if (ValidationMessageClient* client = validationMessageClient()) {
client->showValidationMessage(*m_element, message);
return;
}
ASSERT(!message.isEmpty());
m_message = message;
if (!m_bubble)
m_timer = std::make_unique<Timer>(*this, &ValidationMessage::buildBubbleTree);
else
m_timer = std::make_unique<Timer>(*this, &ValidationMessage::setMessageDOMAndStartTimer);
m_timer->startOneShot(0);
}
void ValidationMessage::setMessageDOMAndStartTimer()
{
ASSERT(!validationMessageClient());
ASSERT(m_messageHeading);
ASSERT(m_messageBody);
m_messageHeading->removeChildren();
m_messageBody->removeChildren();
Vector<String> lines;
m_message.split('\n', lines);
Document& document = m_messageHeading->document();
for (unsigned i = 0; i < lines.size(); ++i) {
if (i) {
m_messageBody->appendChild(Text::create(document, lines[i]), ASSERT_NO_EXCEPTION);
if (i < lines.size() - 1)
m_messageBody->appendChild(HTMLBRElement::create(document), ASSERT_NO_EXCEPTION);
} else
m_messageHeading->setInnerText(lines[i], ASSERT_NO_EXCEPTION);
}
int magnification = document.page() ? document.page()->settings().validationMessageTimerMagnification() : -1;
if (magnification <= 0)
m_timer = nullptr;
else {
m_timer = std::make_unique<Timer>(*this, &ValidationMessage::deleteBubbleTree);
m_timer->startOneShot(std::max(5.0, static_cast<double>(m_message.length()) * magnification / 1000));
}
}
static void adjustBubblePosition(const LayoutRect& hostRect, HTMLElement* bubble)
{
ASSERT(bubble);
if (hostRect.isEmpty())
return;
double hostX = hostRect.x();
double hostY = hostRect.y();
if (RenderObject* renderer = bubble->renderer()) {
if (RenderBox* container = renderer->containingBlock()) {
FloatPoint containerLocation = container->localToAbsolute();
hostX -= containerLocation.x() + container->borderLeft();
hostY -= containerLocation.y() + container->borderTop();
}
}
bubble->setInlineStyleProperty(CSSPropertyTop, hostY + hostRect.height(), CSSPrimitiveValue::CSS_PX);
const int bubbleArrowTopOffset = 32;
double bubbleX = hostX;
if (hostRect.width() / 2 < bubbleArrowTopOffset)
bubbleX = std::max(hostX + hostRect.width() / 2 - bubbleArrowTopOffset, 0.0);
bubble->setInlineStyleProperty(CSSPropertyLeft, bubbleX, CSSPrimitiveValue::CSS_PX);
}
void ValidationMessage::buildBubbleTree()
{
ASSERT(!validationMessageClient());
if (!m_element->renderer())
return;
ShadowRoot& shadowRoot = m_element->ensureUserAgentShadowRoot();
Document& document = m_element->document();
m_bubble = HTMLDivElement::create(document);
m_bubble->setPseudo(AtomicString("-webkit-validation-bubble", AtomicString::ConstructFromLiteral));
m_bubble->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
shadowRoot.appendChild(m_bubble.get(), ASSERT_NO_EXCEPTION);
document.updateLayout();
adjustBubblePosition(m_element->renderer()->absoluteBoundingBoxRect(), m_bubble.get());
RefPtr<HTMLDivElement> clipper = HTMLDivElement::create(document);
clipper->setPseudo(AtomicString("-webkit-validation-bubble-arrow-clipper", AtomicString::ConstructFromLiteral));
RefPtr<HTMLDivElement> bubbleArrow = HTMLDivElement::create(document);
bubbleArrow->setPseudo(AtomicString("-webkit-validation-bubble-arrow", AtomicString::ConstructFromLiteral));
clipper->appendChild(bubbleArrow.release(), ASSERT_NO_EXCEPTION);
m_bubble->appendChild(clipper.release(), ASSERT_NO_EXCEPTION);
RefPtr<HTMLElement> message = HTMLDivElement::create(document);
message->setPseudo(AtomicString("-webkit-validation-bubble-message", AtomicString::ConstructFromLiteral));
RefPtr<HTMLElement> icon = HTMLDivElement::create(document);
icon->setPseudo(AtomicString("-webkit-validation-bubble-icon", AtomicString::ConstructFromLiteral));
message->appendChild(icon.release(), ASSERT_NO_EXCEPTION);
RefPtr<HTMLElement> textBlock = HTMLDivElement::create(document);
textBlock->setPseudo(AtomicString("-webkit-validation-bubble-text-block", AtomicString::ConstructFromLiteral));
m_messageHeading = HTMLDivElement::create(document);
m_messageHeading->setPseudo(AtomicString("-webkit-validation-bubble-heading", AtomicString::ConstructFromLiteral));
textBlock->appendChild(m_messageHeading, ASSERT_NO_EXCEPTION);
m_messageBody = HTMLDivElement::create(document);
m_messageBody->setPseudo(AtomicString("-webkit-validation-bubble-body", AtomicString::ConstructFromLiteral));
textBlock->appendChild(m_messageBody, ASSERT_NO_EXCEPTION);
message->appendChild(textBlock.release(), ASSERT_NO_EXCEPTION);
m_bubble->appendChild(message.release(), ASSERT_NO_EXCEPTION);
setMessageDOMAndStartTimer();
}
void ValidationMessage::requestToHideMessage()
{
if (ValidationMessageClient* client = validationMessageClient()) {
client->hideValidationMessage(*m_element);
return;
}
m_timer = std::make_unique<Timer>(*this, &ValidationMessage::deleteBubbleTree);
m_timer->startOneShot(0);
}
bool ValidationMessage::shadowTreeContains(const Node& node) const
{
if (validationMessageClient() || !m_bubble)
return false;
return &m_bubble->treeScope() == &node.treeScope();
}
void ValidationMessage::deleteBubbleTree()
{
ASSERT(!validationMessageClient());
if (m_bubble) {
m_messageHeading = nullptr;
m_messageBody = nullptr;
m_element->userAgentShadowRoot()->removeChild(m_bubble.get(), ASSERT_NO_EXCEPTION);
m_bubble = nullptr;
}
m_message = String();
}
bool ValidationMessage::isVisible() const
{
if (ValidationMessageClient* client = validationMessageClient())
return client->isValidationMessageVisible(*m_element);
return !m_message.isEmpty();
}
}