HTML5 <details> and <summary>: rendering
[WebKit-https.git] / Source / WebCore / html / HTMLDetailsElement.cpp
1 /*
2  * Copyright (C) 2010, 2011 Nokia Corporation and/or its subsidiary(-ies)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "HTMLDetailsElement.h"
23
24 #include "Frame.h"
25 #include "HTMLNames.h"
26 #include "MouseEvent.h"
27 #include "PlatformMouseEvent.h"
28 #include "RenderDetails.h"
29
30 namespace WebCore {
31
32 using namespace HTMLNames;
33
34 PassRefPtr<HTMLDetailsElement> HTMLDetailsElement::create(const QualifiedName& tagName, Document* document)
35 {
36     return adoptRef(new HTMLDetailsElement(tagName, document));
37 }
38
39 HTMLDetailsElement::HTMLDetailsElement(const QualifiedName& tagName, Document* document)
40     : HTMLElement(tagName, document)
41     , m_mainSummary(0)
42     , m_isOpen(false)
43 {
44     ASSERT(hasTagName(detailsTag));
45 }
46
47 RenderObject* HTMLDetailsElement::createRenderer(RenderArena* arena, RenderStyle*)
48 {
49     return new (arena) RenderDetails(this);
50 }
51
52 void HTMLDetailsElement::findMainSummary()
53 {
54     m_mainSummary = 0;
55
56     for (Node* child = firstChild(); child; child = child->nextSibling()) {
57         if (child->hasTagName(summaryTag)) {
58             m_mainSummary = child;
59             break;
60         }
61     }
62 }
63
64 void HTMLDetailsElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
65 {
66     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
67     if (!changedByParser) {
68         Node* oldSummary = m_mainSummary;
69         findMainSummary();
70
71         if (oldSummary != m_mainSummary && !m_isOpen && attached()) {
72             if (oldSummary && oldSummary->attached())
73                 oldSummary->detach();
74             if (m_mainSummary && childCountDelta < 0 && !m_mainSummary->renderer()) {
75                 // If childCountDelta is less then zero and the main summary has changed it must be because previous main
76                 // summary was removed. The new main summary was then inside the unrevealed content and needs to be
77                 // reattached to create its renderer. If childCountDelta is not less then zero then a new <summary> element
78                 // has been added and it will be attached without our help.
79                 m_mainSummary->detach();
80                 m_mainSummary->attach();
81             }
82         }
83     }
84 }
85
86 void HTMLDetailsElement::finishParsingChildren()
87 {
88     HTMLElement::finishParsingChildren();
89     findMainSummary();
90     if (attached() && m_mainSummary && !m_mainSummary->renderer()) {
91         m_mainSummary->detach();
92         m_mainSummary->attach();
93     }
94 }
95
96 void HTMLDetailsElement::parseMappedAttribute(Attribute* attr)
97 {
98     if (attr->name() == openAttr) {
99         bool oldValue = m_isOpen;
100         m_isOpen =  !attr->value().isNull();
101         if (attached() && oldValue != m_isOpen) {
102             detach();
103             attach();
104         }
105     } else
106         HTMLElement::parseMappedAttribute(attr);
107 }
108
109 bool HTMLDetailsElement::childShouldCreateRenderer(Node* child) const
110 {
111     return m_isOpen || child == m_mainSummary;
112 }
113
114 void HTMLDetailsElement::defaultEventHandler(Event* event)
115 {
116     HTMLElement::defaultEventHandler(event);
117
118     if (!renderer() || !renderer()->isDetails() || !event->isMouseEvent() || event->type() != eventNames().clickEvent || event->defaultHandled())
119         return;
120
121     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
122     if (mouseEvent->button() != LeftButton)
123         return;
124
125     RenderDetails* renderDetails = static_cast<RenderDetails*>(renderer());
126
127     float factor = document() && document()->frame() ? document()->frame()->pageZoomFactor() : 1.0;
128     FloatPoint pos = renderDetails->absoluteToLocal(FloatPoint(mouseEvent->pageX() * factor, mouseEvent->pageY() * factor));
129
130     if (renderDetails->interactiveArea().contains(pos.x(), pos.y())) {
131         setAttribute(openAttr, m_isOpen ? String() : String(""));
132         event->setDefaultHandled();
133     }
134 }
135
136 }