1/*
2 * Copyright (C) 2010 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 "HTMLMeterElement.h"
23
24#if ENABLE(METER_ELEMENT)
25
26#include "Attribute.h"
27#include "ElementIterator.h"
28#include "HTMLDivElement.h"
29#include "HTMLFormElement.h"
30#include "HTMLNames.h"
31#include "HTMLParserIdioms.h"
32#include "HTMLStyleElement.h"
33#include "Page.h"
34#include "RenderMeter.h"
35#include "RenderTheme.h"
36#include "ShadowRoot.h"
37#include "UserAgentStyleSheets.h"
38#include <wtf/IsoMallocInlines.h>
39
40namespace WebCore {
41
42WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLMeterElement);
43
44using namespace HTMLNames;
45
46HTMLMeterElement::HTMLMeterElement(const QualifiedName& tagName, Document& document)
47 : LabelableElement(tagName, document)
48{
49 ASSERT(hasTagName(meterTag));
50}
51
52HTMLMeterElement::~HTMLMeterElement() = default;
53
54Ref<HTMLMeterElement> HTMLMeterElement::create(const QualifiedName& tagName, Document& document)
55{
56 Ref<HTMLMeterElement> meter = adoptRef(*new HTMLMeterElement(tagName, document));
57 meter->ensureUserAgentShadowRoot();
58 return meter;
59}
60
61RenderPtr<RenderElement> HTMLMeterElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
62{
63 if (!RenderTheme::singleton().supportsMeter(style.appearance()))
64 return RenderElement::createFor(*this, WTFMove(style));
65
66 return createRenderer<RenderMeter>(*this, WTFMove(style));
67}
68
69bool HTMLMeterElement::childShouldCreateRenderer(const Node& child) const
70{
71 return !is<RenderMeter>(renderer()) && HTMLElement::childShouldCreateRenderer(child);
72}
73
74void HTMLMeterElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
75{
76 if (name == valueAttr || name == minAttr || name == maxAttr || name == lowAttr || name == highAttr || name == optimumAttr)
77 didElementStateChange();
78 else
79 LabelableElement::parseAttribute(name, value);
80}
81
82double HTMLMeterElement::min() const
83{
84 return parseToDoubleForNumberType(attributeWithoutSynchronization(minAttr), 0);
85}
86
87void HTMLMeterElement::setMin(double min)
88{
89 setAttributeWithoutSynchronization(minAttr, AtomicString::number(min));
90}
91
92double HTMLMeterElement::max() const
93{
94 return std::max(parseToDoubleForNumberType(attributeWithoutSynchronization(maxAttr), std::max(1.0, min())), min());
95}
96
97void HTMLMeterElement::setMax(double max)
98{
99 setAttributeWithoutSynchronization(maxAttr, AtomicString::number(max));
100}
101
102double HTMLMeterElement::value() const
103{
104 double value = parseToDoubleForNumberType(attributeWithoutSynchronization(valueAttr), 0);
105 return std::min(std::max(value, min()), max());
106}
107
108void HTMLMeterElement::setValue(double value)
109{
110 setAttributeWithoutSynchronization(valueAttr, AtomicString::number(value));
111}
112
113double HTMLMeterElement::low() const
114{
115 double low = parseToDoubleForNumberType(attributeWithoutSynchronization(lowAttr), min());
116 return std::min(std::max(low, min()), max());
117}
118
119void HTMLMeterElement::setLow(double low)
120{
121 setAttributeWithoutSynchronization(lowAttr, AtomicString::number(low));
122}
123
124double HTMLMeterElement::high() const
125{
126 double high = parseToDoubleForNumberType(attributeWithoutSynchronization(highAttr), max());
127 return std::min(std::max(high, low()), max());
128}
129
130void HTMLMeterElement::setHigh(double high)
131{
132 setAttributeWithoutSynchronization(highAttr, AtomicString::number(high));
133}
134
135double HTMLMeterElement::optimum() const
136{
137 double optimum = parseToDoubleForNumberType(attributeWithoutSynchronization(optimumAttr), (max() + min()) / 2);
138 return std::min(std::max(optimum, min()), max());
139}
140
141void HTMLMeterElement::setOptimum(double optimum)
142{
143 setAttributeWithoutSynchronization(optimumAttr, AtomicString::number(optimum));
144}
145
146HTMLMeterElement::GaugeRegion HTMLMeterElement::gaugeRegion() const
147{
148 double lowValue = low();
149 double highValue = high();
150 double theValue = value();
151 double optimumValue = optimum();
152
153 if (optimumValue < lowValue) {
154 // The optimum range stays under low
155 if (theValue <= lowValue)
156 return GaugeRegionOptimum;
157 if (theValue <= highValue)
158 return GaugeRegionSuboptimal;
159 return GaugeRegionEvenLessGood;
160 }
161
162 if (highValue < optimumValue) {
163 // The optimum range stays over high
164 if (highValue <= theValue)
165 return GaugeRegionOptimum;
166 if (lowValue <= theValue)
167 return GaugeRegionSuboptimal;
168 return GaugeRegionEvenLessGood;
169 }
170
171 // The optimum range stays between high and low.
172 // According to the standard, <meter> never show GaugeRegionEvenLessGood in this case
173 // because the value is never less or greater than min or max.
174 if (lowValue <= theValue && theValue <= highValue)
175 return GaugeRegionOptimum;
176 return GaugeRegionSuboptimal;
177}
178
179double HTMLMeterElement::valueRatio() const
180{
181 double min = this->min();
182 double max = this->max();
183 double value = this->value();
184
185 if (max <= min)
186 return 0;
187 return (value - min) / (max - min);
188}
189
190static void setValueClass(HTMLElement& element, HTMLMeterElement::GaugeRegion gaugeRegion)
191{
192 switch (gaugeRegion) {
193 case HTMLMeterElement::GaugeRegionOptimum:
194 element.setAttribute(HTMLNames::classAttr, "optimum");
195 element.setPseudo("-webkit-meter-optimum-value");
196 return;
197 case HTMLMeterElement::GaugeRegionSuboptimal:
198 element.setAttribute(HTMLNames::classAttr, "suboptimum");
199 element.setPseudo("-webkit-meter-suboptimum-value");
200 return;
201 case HTMLMeterElement::GaugeRegionEvenLessGood:
202 element.setAttribute(HTMLNames::classAttr, "even-less-good");
203 element.setPseudo("-webkit-meter-even-less-good-value");
204 return;
205 default:
206 ASSERT_NOT_REACHED();
207 }
208}
209
210void HTMLMeterElement::didElementStateChange()
211{
212 m_value->setInlineStyleProperty(CSSPropertyWidth, valueRatio()*100, CSSPrimitiveValue::CSS_PERCENTAGE);
213 setValueClass(*m_value, gaugeRegion());
214
215 if (RenderMeter* render = renderMeter())
216 render->updateFromElement();
217}
218
219RenderMeter* HTMLMeterElement::renderMeter() const
220{
221 if (is<RenderMeter>(renderer()))
222 return downcast<RenderMeter>(renderer());
223 return nullptr;
224}
225
226void HTMLMeterElement::didAddUserAgentShadowRoot(ShadowRoot& root)
227{
228 ASSERT(!m_value);
229
230 static NeverDestroyed<String> shadowStyle(meterElementShadowUserAgentStyleSheet, String::ConstructFromLiteral);
231
232 auto style = HTMLStyleElement::create(HTMLNames::styleTag, document(), false);
233 style->setTextContent(shadowStyle);
234 root.appendChild(style);
235
236 // Pseudos are set to allow author styling.
237 auto inner = HTMLDivElement::create(document());
238 inner->setIdAttribute("inner");
239 inner->setPseudo("-webkit-meter-inner-element");
240 root.appendChild(inner);
241
242 auto bar = HTMLDivElement::create(document());
243 bar->setIdAttribute("bar");
244 bar->setPseudo("-webkit-meter-bar");
245 inner->appendChild(bar);
246
247 m_value = HTMLDivElement::create(document());
248 m_value->setIdAttribute("value");
249 bar->appendChild(*m_value);
250
251 didElementStateChange();
252}
253
254} // namespace
255#endif
256