1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Peter Kelly (pmk@post.com)
5 * (C) 2001 Dirk Mueller (mueller@kde.org)
6 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25 #ifndef Element_h
26 #define Element_h
27
28 #include "Document.h"
29 #include "FragmentScriptingPermission.h"
30 #include "NamedNodeMap.h"
31 #include "ScrollTypes.h"
32
33 namespace WebCore {
34
35 class Attribute;
36 class ClientRect;
37 class ClientRectList;
38 class DOMStringMap;
39 class DOMTokenList;
40 class ElementRareData;
41 class IntSize;
42 class ShadowRoot;
43 class WebKitAnimationList;
44
45 enum SpellcheckAttributeState {
46 SpellcheckAttributeTrue,
47 SpellcheckAttributeFalse,
48 SpellcheckAttributeDefault
49 };
50
51 class Element : public ContainerNode {
52 public:
53 static PassRefPtr<Element> create(const QualifiedName&, Document*);
54 virtual ~Element();
55
56 DEFINE_ATTRIBUTE_EVENT_LISTENER(abort);
57 DEFINE_ATTRIBUTE_EVENT_LISTENER(change);
58 DEFINE_ATTRIBUTE_EVENT_LISTENER(click);
59 DEFINE_ATTRIBUTE_EVENT_LISTENER(contextmenu);
60 DEFINE_ATTRIBUTE_EVENT_LISTENER(dblclick);
61 DEFINE_ATTRIBUTE_EVENT_LISTENER(dragenter);
62 DEFINE_ATTRIBUTE_EVENT_LISTENER(dragover);
63 DEFINE_ATTRIBUTE_EVENT_LISTENER(dragleave);
64 DEFINE_ATTRIBUTE_EVENT_LISTENER(drop);
65 DEFINE_ATTRIBUTE_EVENT_LISTENER(dragstart);
66 DEFINE_ATTRIBUTE_EVENT_LISTENER(drag);
67 DEFINE_ATTRIBUTE_EVENT_LISTENER(dragend);
68 DEFINE_ATTRIBUTE_EVENT_LISTENER(input);
69 DEFINE_ATTRIBUTE_EVENT_LISTENER(invalid);
70 DEFINE_ATTRIBUTE_EVENT_LISTENER(keydown);
71 DEFINE_ATTRIBUTE_EVENT_LISTENER(keypress);
72 DEFINE_ATTRIBUTE_EVENT_LISTENER(keyup);
73 DEFINE_ATTRIBUTE_EVENT_LISTENER(mousedown);
74 DEFINE_ATTRIBUTE_EVENT_LISTENER(mousemove);
75 DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseout);
76 DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseover);
77 DEFINE_ATTRIBUTE_EVENT_LISTENER(mouseup);
78 DEFINE_ATTRIBUTE_EVENT_LISTENER(mousewheel);
79 DEFINE_ATTRIBUTE_EVENT_LISTENER(scroll);
80 DEFINE_ATTRIBUTE_EVENT_LISTENER(select);
81 DEFINE_ATTRIBUTE_EVENT_LISTENER(submit);
82
83 // These four attribute event handler attributes are overridden by HTMLBodyElement
84 // and HTMLFrameSetElement to forward to the DOMWindow.
85 DECLARE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(blur);
86 DECLARE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(error);
87 DECLARE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(focus);
88 DECLARE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(load);
89
90 // WebKit extensions
91 DEFINE_ATTRIBUTE_EVENT_LISTENER(beforecut);
92 DEFINE_ATTRIBUTE_EVENT_LISTENER(cut);
93 DEFINE_ATTRIBUTE_EVENT_LISTENER(beforecopy);
94 DEFINE_ATTRIBUTE_EVENT_LISTENER(copy);
95 DEFINE_ATTRIBUTE_EVENT_LISTENER(beforepaste);
96 DEFINE_ATTRIBUTE_EVENT_LISTENER(paste);
97 DEFINE_ATTRIBUTE_EVENT_LISTENER(reset);
98 DEFINE_ATTRIBUTE_EVENT_LISTENER(search);
99 DEFINE_ATTRIBUTE_EVENT_LISTENER(selectstart);
100 #if ENABLE(TOUCH_EVENTS)
101 DEFINE_ATTRIBUTE_EVENT_LISTENER(touchstart);
102 DEFINE_ATTRIBUTE_EVENT_LISTENER(touchmove);
103 DEFINE_ATTRIBUTE_EVENT_LISTENER(touchend);
104 DEFINE_ATTRIBUTE_EVENT_LISTENER(touchcancel);
105 #endif
106 #if ENABLE(FULLSCREEN_API)
107 DEFINE_ATTRIBUTE_EVENT_LISTENER(webkitfullscreenchange);
108 #endif
109
110 virtual PassRefPtr<DocumentFragment> deprecatedCreateContextualFragment(const String&, FragmentScriptingPermission = FragmentScriptingAllowed);
111
112 bool hasAttribute(const QualifiedName&) const;
113 const AtomicString& getAttribute(const QualifiedName&) const;
114 void setAttribute(const QualifiedName&, const AtomicString& value, ExceptionCode&);
115 void removeAttribute(const QualifiedName&, ExceptionCode&);
116
117 // Typed getters and setters for language bindings.
118 int getIntegralAttribute(const QualifiedName& attributeName) const;
119 void setIntegralAttribute(const QualifiedName& attributeName, int value);
120 unsigned getUnsignedIntegralAttribute(const QualifiedName& attributeName) const;
121 void setUnsignedIntegralAttribute(const QualifiedName& attributeName, unsigned value);
122
123 // Call this to get the value of an attribute that is known not to be the style
124 // attribute or one of the SVG animatable attributes.
125 bool fastHasAttribute(const QualifiedName&) const;
126 const AtomicString& fastGetAttribute(const QualifiedName&) const;
127
128 bool hasAttributes() const;
129
130 bool hasAttribute(const String& name) const;
131 bool hasAttributeNS(const String& namespaceURI, const String& localName) const;
132
133 const AtomicString& getAttribute(const String& name) const;
134 const AtomicString& getAttributeNS(const String& namespaceURI, const String& localName) const;
135
136 void setAttribute(const AtomicString& name, const AtomicString& value, ExceptionCode&);
137 void setAttributeNS(const AtomicString& namespaceURI, const AtomicString& qualifiedName, const AtomicString& value, ExceptionCode&, FragmentScriptingPermission = FragmentScriptingAllowed);
138
139 bool isIdAttributeName(const QualifiedName&) const;
140 const AtomicString& getIdAttribute() const;
141 void setIdAttribute(const AtomicString&);
142
143 // Call this to get the value of the id attribute for style resolution purposes.
144 // The value will already be lowercased if the document is in compatibility mode,
145 // so this function is not suitable for non-style uses.
146 const AtomicString& idForStyleResolution() const;
147
148 void scrollIntoView(bool alignToTop = true);
149 void scrollIntoViewIfNeeded(bool centerIfNeeded = true);
150
151 void scrollByLines(int lines);
152 void scrollByPages(int pages);
153
154 int offsetLeft();
155 int offsetTop();
156 int offsetWidth();
157 int offsetHeight();
158 Element* offsetParent();
159 int clientLeft();
160 int clientTop();
161 int clientWidth();
162 int clientHeight();
163 virtual int scrollLeft() const;
164 virtual int scrollTop() const;
165 virtual void setScrollLeft(int);
166 virtual void setScrollTop(int);
167 virtual int scrollWidth() const;
168 virtual int scrollHeight() const;
169
170 IntRect boundsInWindowSpace() const;
171
172 PassRefPtr<ClientRectList> getClientRects() const;
173 PassRefPtr<ClientRect> getBoundingClientRect() const;
174
175 // Returns the absolute bounding box translated into screen coordinates:
176 IntRect screenRect() const;
177
178 void removeAttribute(const String& name, ExceptionCode&);
179 void removeAttributeNS(const String& namespaceURI, const String& localName, ExceptionCode&);
180
181 PassRefPtr<Attr> getAttributeNode(const String& name);
182 PassRefPtr<Attr> getAttributeNodeNS(const String& namespaceURI, const String& localName);
183 PassRefPtr<Attr> setAttributeNode(Attr*, ExceptionCode&);
184 PassRefPtr<Attr> setAttributeNodeNS(Attr*, ExceptionCode&);
185 PassRefPtr<Attr> removeAttributeNode(Attr*, ExceptionCode&);
186
187 virtual CSSStyleDeclaration* style();
188
tagQName()189 const QualifiedName& tagQName() const { return m_tagName; }
tagName()190 String tagName() const { return nodeName(); }
hasTagName(const QualifiedName & tagName)191 bool hasTagName(const QualifiedName& tagName) const { return m_tagName.matches(tagName); }
192
193 // A fast function for checking the local name against another atomic string.
hasLocalName(const AtomicString & other)194 bool hasLocalName(const AtomicString& other) const { return m_tagName.localName() == other; }
hasLocalName(const QualifiedName & other)195 bool hasLocalName(const QualifiedName& other) const { return m_tagName.localName() == other.localName(); }
196
localName()197 const AtomicString& localName() const { return m_tagName.localName(); }
prefix()198 const AtomicString& prefix() const { return m_tagName.prefix(); }
namespaceURI()199 const AtomicString& namespaceURI() const { return m_tagName.namespaceURI(); }
200
201 virtual KURL baseURI() const;
202
203 virtual String nodeName() const;
204
205 PassRefPtr<Element> cloneElementWithChildren();
206 PassRefPtr<Element> cloneElementWithoutChildren();
207
208 void normalizeAttributes();
209 String nodeNamePreservingCase() const;
210
211 // convenience methods which ignore exceptions
212 void setAttribute(const QualifiedName&, const AtomicString& value);
213 void setBooleanAttribute(const QualifiedName& name, bool);
214 // Please don't use setCStringAttribute in performance-sensitive code;
215 // use a static AtomicString value instead to avoid the conversion overhead.
216 void setCStringAttribute(const QualifiedName&, const char* cStringValue);
217
218 NamedNodeMap* attributes(bool readonly = false) const;
219
220 // This method is called whenever an attribute is added, changed or removed.
221 virtual void attributeChanged(Attribute*, bool preserveDecls = false);
222
223 void setAttributeMap(PassRefPtr<NamedNodeMap>, FragmentScriptingPermission = FragmentScriptingAllowed);
attributeMap()224 NamedNodeMap* attributeMap() const { return m_attributeMap.get(); }
225
226 virtual void copyNonAttributeProperties(const Element* source);
227
228 virtual void attach();
229 virtual void detach();
230 virtual RenderObject* createRenderer(RenderArena*, RenderStyle*);
231 virtual void recalcStyle(StyleChange = NoChange);
232
233 ShadowRoot* shadowRoot() const;
234 ShadowRoot* ensureShadowRoot();
235 void removeShadowRoot();
236
237 virtual const AtomicString& shadowPseudoId() const;
238
239 RenderStyle* computedStyle(PseudoId = NOPSEUDO);
240
241 AtomicString computeInheritedLanguage() const;
242
243 void dispatchAttrRemovalEvent(Attribute*);
244 void dispatchAttrAdditionEvent(Attribute*);
245
accessKeyAction(bool)246 virtual void accessKeyAction(bool /*sendToAnyEvent*/) { }
247
248 virtual bool isURLAttribute(Attribute*) const;
249
250 KURL getURLAttribute(const QualifiedName&) const;
251 KURL getNonEmptyURLAttribute(const QualifiedName&) const;
252
253 virtual const QualifiedName& imageSourceAttributeName() const;
target()254 virtual String target() const { return String(); }
255
256 virtual void focus(bool restorePreviousSelection = true);
257 virtual void updateFocusAppearance(bool restorePreviousSelection);
258 void blur();
259
260 String innerText() const;
261 String outerText() const;
262
263 virtual String title() const;
264
265 String openTagStartToString() const;
266
267 void updateId(const AtomicString& oldId, const AtomicString& newId);
268
269 IntSize minimumSizeForResizing() const;
270 void setMinimumSizeForResizing(const IntSize&);
271
272 // Use Document::registerForDocumentActivationCallbacks() to subscribe to these
documentWillBecomeInactive()273 virtual void documentWillBecomeInactive() { }
documentDidBecomeActive()274 virtual void documentDidBecomeActive() { }
275
276 // Use Document::registerForMediaVolumeCallbacks() to subscribe to this
mediaVolumeDidChange()277 virtual void mediaVolumeDidChange() { }
278
279 // Use Document::registerForPrivateBrowsingStateChangedCallbacks() to subscribe to this.
privateBrowsingStateDidChange()280 virtual void privateBrowsingStateDidChange() { }
281
isFinishedParsingChildren()282 bool isFinishedParsingChildren() const { return isParsingChildrenFinished(); }
283 virtual void finishParsingChildren();
284 virtual void beginParsingChildren();
285
286 // ElementTraversal API
287 Element* firstElementChild() const;
288 Element* lastElementChild() const;
289 Element* previousElementSibling() const;
290 Element* nextElementSibling() const;
291 unsigned childElementCount() const;
292
293 bool webkitMatchesSelector(const String& selectors, ExceptionCode&);
294
295 DOMTokenList* classList();
296 DOMTokenList* optionalClassList() const;
297
298 DOMStringMap* dataset();
299
300 #if ENABLE(MATHML)
isMathMLElement()301 virtual bool isMathMLElement() const { return false; }
302 #else
isMathMLElement()303 static bool isMathMLElement() { return false; }
304 #endif
305
306 #if ENABLE(VIDEO)
isMediaElement()307 virtual bool isMediaElement() const { return false; }
308 #endif
309
310 #if ENABLE(INPUT_SPEECH)
isInputFieldSpeechButtonElement()311 virtual bool isInputFieldSpeechButtonElement() const { return false; }
312 #endif
313
isFormControlElement()314 virtual bool isFormControlElement() const { return false; }
isEnabledFormControl()315 virtual bool isEnabledFormControl() const { return true; }
isReadOnlyFormControl()316 virtual bool isReadOnlyFormControl() const { return false; }
isSpinButtonElement()317 virtual bool isSpinButtonElement() const { return false; }
isTextFormControl()318 virtual bool isTextFormControl() const { return false; }
isOptionalFormControl()319 virtual bool isOptionalFormControl() const { return false; }
isRequiredFormControl()320 virtual bool isRequiredFormControl() const { return false; }
isDefaultButtonForForm()321 virtual bool isDefaultButtonForForm() const { return false; }
willValidate()322 virtual bool willValidate() const { return false; }
isValidFormControlElement()323 virtual bool isValidFormControlElement() { return false; }
hasUnacceptableValue()324 virtual bool hasUnacceptableValue() const { return false; }
isInRange()325 virtual bool isInRange() const { return false; }
isOutOfRange()326 virtual bool isOutOfRange() const { return false; }
isFrameElementBase()327 virtual bool isFrameElementBase() const { return false; }
328
formControlValueMatchesRenderer()329 virtual bool formControlValueMatchesRenderer() const { return false; }
setFormControlValueMatchesRenderer(bool)330 virtual void setFormControlValueMatchesRenderer(bool) { }
331
formControlName()332 virtual const AtomicString& formControlName() const { return nullAtom; }
formControlType()333 virtual const AtomicString& formControlType() const { return nullAtom; }
334
shouldSaveAndRestoreFormControlState()335 virtual bool shouldSaveAndRestoreFormControlState() const { return true; }
saveFormControlState(String &)336 virtual bool saveFormControlState(String&) const { return false; }
restoreFormControlState(const String &)337 virtual void restoreFormControlState(const String&) { }
338
339 virtual bool wasChangedSinceLastFormControlChangeEvent() const;
340 virtual void setChangedSinceLastFormControlChangeEvent(bool);
dispatchFormControlChangeEvent()341 virtual void dispatchFormControlChangeEvent() { }
342
343 #if ENABLE(SVG)
344 virtual bool childShouldCreateRenderer(Node*) const;
345 #endif
346
347 #if ENABLE(FULLSCREEN_API)
348 enum {
349 ALLOW_KEYBOARD_INPUT = 1
350 };
351
352 void webkitRequestFullScreen(unsigned short flags);
353 #endif
354
355 virtual bool isSpellCheckingEnabled() const;
356
357 PassRefPtr<WebKitAnimationList> webkitGetAnimations() const;
358
359 protected:
Element(const QualifiedName & tagName,Document * document,ConstructionType type)360 Element(const QualifiedName& tagName, Document* document, ConstructionType type)
361 : ContainerNode(document, type)
362 , m_tagName(tagName)
363 {
364 }
365
366 virtual void insertedIntoDocument();
367 virtual void removedFromDocument();
368 virtual void insertedIntoTree(bool);
369 virtual void removedFromTree(bool);
370 virtual void childrenChanged(bool changedByParser = false, Node* beforeChange = 0, Node* afterChange = 0, int childCountDelta = 0);
371
372 // The implementation of Element::attributeChanged() calls the following two functions.
373 // They are separated to allow a different flow of control in StyledElement::attributeChanged().
374 void recalcStyleIfNeededAfterAttributeChanged(Attribute*);
375 void updateAfterAttributeChanged(Attribute*);
376
377 void idAttributeChanged(Attribute*);
378
379 private:
380 void scrollByUnits(int units, ScrollGranularity);
381
382 virtual void setPrefix(const AtomicString&, ExceptionCode&);
383 virtual NodeType nodeType() const;
384 virtual bool childTypeAllowed(NodeType) const;
385
386 virtual PassRefPtr<Attribute> createAttribute(const QualifiedName&, const AtomicString& value);
387
388 #ifndef NDEBUG
389 virtual void formatForDebugger(char* buffer, unsigned length) const;
390 #endif
391
392 bool pseudoStyleCacheIsInvalid(const RenderStyle* currentStyle, RenderStyle* newStyle);
393
394 void createAttributeMap() const;
395
updateStyleAttribute()396 virtual void updateStyleAttribute() const { }
397
398 #if ENABLE(SVG)
updateAnimatedSVGAttribute(const QualifiedName &)399 virtual void updateAnimatedSVGAttribute(const QualifiedName&) const { }
400 #endif
401
402 void cancelFocusAppearanceUpdate();
403
virtualPrefix()404 virtual const AtomicString& virtualPrefix() const { return prefix(); }
virtualLocalName()405 virtual const AtomicString& virtualLocalName() const { return localName(); }
virtualNamespaceURI()406 virtual const AtomicString& virtualNamespaceURI() const { return namespaceURI(); }
407 virtual RenderStyle* virtualComputedStyle(PseudoId pseudoElementSpecifier = NOPSEUDO) { return computedStyle(pseudoElementSpecifier); }
408
409 // cloneNode is private so that non-virtual cloneElementWithChildren and cloneElementWithoutChildren
410 // are used instead.
411 virtual PassRefPtr<Node> cloneNode(bool deep);
412 virtual PassRefPtr<Element> cloneElementWithoutAttributesAndChildren() const;
413
414 QualifiedName m_tagName;
415 virtual NodeRareData* createRareData();
416
417 ElementRareData* rareData() const;
418 ElementRareData* ensureRareData();
419
420 SpellcheckAttributeState spellcheckAttributeState() const;
421
422 private:
423 mutable RefPtr<NamedNodeMap> m_attributeMap;
424 };
425
toElement(Node * node)426 inline Element* toElement(Node* node)
427 {
428 ASSERT(!node || node->isElementNode());
429 return static_cast<Element*>(node);
430 }
431
toElement(const Node * node)432 inline const Element* toElement(const Node* node)
433 {
434 ASSERT(!node || node->isElementNode());
435 return static_cast<const Element*>(node);
436 }
437
438 // This will catch anyone doing an unnecessary cast.
439 void toElement(const Element*);
440
hasTagName(const QualifiedName & name)441 inline bool Node::hasTagName(const QualifiedName& name) const
442 {
443 return isElementNode() && toElement(this)->hasTagName(name);
444 }
445
hasLocalName(const AtomicString & name)446 inline bool Node::hasLocalName(const AtomicString& name) const
447 {
448 return isElementNode() && toElement(this)->hasLocalName(name);
449 }
450
hasAttributes()451 inline bool Node::hasAttributes() const
452 {
453 return isElementNode() && toElement(this)->hasAttributes();
454 }
455
attributes()456 inline NamedNodeMap* Node::attributes() const
457 {
458 return isElementNode() ? toElement(this)->attributes() : 0;
459 }
460
parentElement()461 inline Element* Node::parentElement() const
462 {
463 ContainerNode* parent = parentNode();
464 return parent && parent->isElementNode() ? toElement(parent) : 0;
465 }
466
attributes(bool readonly)467 inline NamedNodeMap* Element::attributes(bool readonly) const
468 {
469 if (!isStyleAttributeValid())
470 updateStyleAttribute();
471
472 #if ENABLE(SVG)
473 if (!areSVGAttributesValid())
474 updateAnimatedSVGAttribute(anyQName());
475 #endif
476
477 if (!readonly && !m_attributeMap)
478 createAttributeMap();
479 return m_attributeMap.get();
480 }
481
updateId(const AtomicString & oldId,const AtomicString & newId)482 inline void Element::updateId(const AtomicString& oldId, const AtomicString& newId)
483 {
484 if (!inDocument())
485 return;
486
487 if (oldId == newId)
488 return;
489
490 TreeScope* scope = treeScope();
491 if (!oldId.isEmpty())
492 scope->removeElementById(oldId, this);
493 if (!newId.isEmpty())
494 scope->addElementById(newId, this);
495 }
496
fastHasAttribute(const QualifiedName & name)497 inline bool Element::fastHasAttribute(const QualifiedName& name) const
498 {
499 return m_attributeMap && m_attributeMap->getAttributeItem(name);
500 }
501
fastGetAttribute(const QualifiedName & name)502 inline const AtomicString& Element::fastGetAttribute(const QualifiedName& name) const
503 {
504 if (m_attributeMap) {
505 if (Attribute* attribute = m_attributeMap->getAttributeItem(name))
506 return attribute->value();
507 }
508 return nullAtom;
509 }
510
idForStyleResolution()511 inline const AtomicString& Element::idForStyleResolution() const
512 {
513 ASSERT(hasID());
514 return m_attributeMap->idForStyleResolution();
515 }
516
isIdAttributeName(const QualifiedName & attributeName)517 inline bool Element::isIdAttributeName(const QualifiedName& attributeName) const
518 {
519 // FIXME: This check is probably not correct for the case where the document has an id attribute
520 // with a non-null namespace, because it will return false, a false negative, if the prefixes
521 // don't match but the local name and namespace both do. However, since this has been like this
522 // for a while and the code paths may be hot, we'll have to measure performance if we fix it.
523 return attributeName == document()->idAttributeName();
524 }
525
getIdAttribute()526 inline const AtomicString& Element::getIdAttribute() const
527 {
528 return fastGetAttribute(document()->idAttributeName());
529 }
530
setIdAttribute(const AtomicString & value)531 inline void Element::setIdAttribute(const AtomicString& value)
532 {
533 setAttribute(document()->idAttributeName(), value);
534 }
535
shadowPseudoId()536 inline const AtomicString& Element::shadowPseudoId() const
537 {
538 return nullAtom;
539 }
540
firstElementChild(const ContainerNode * container)541 inline Element* firstElementChild(const ContainerNode* container)
542 {
543 ASSERT_ARG(container, container);
544 Node* child = container->firstChild();
545 while (child && !child->isElementNode())
546 child = child->nextSibling();
547 return static_cast<Element*>(child);
548 }
549
550 } // namespace
551
552 #endif
553