Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 2011, 2013 Apple Inc. All rights reserved.
      3  * Copyright (C) 2014 Samsung Electronics. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     18  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #include "config.h"
     28 #include "core/dom/SelectorQuery.h"
     29 
     30 #include "bindings/core/v8/ExceptionState.h"
     31 #include "core/css/SelectorChecker.h"
     32 #include "core/css/SiblingTraversalStrategies.h"
     33 #include "core/css/parser/CSSParser.h"
     34 #include "core/dom/Document.h"
     35 #include "core/dom/ElementTraversal.h"
     36 #include "core/dom/Node.h"
     37 #include "core/dom/StaticNodeList.h"
     38 #include "core/dom/shadow/ElementShadow.h"
     39 #include "core/dom/shadow/ShadowRoot.h"
     40 
     41 namespace blink {
     42 
     43 struct SingleElementSelectorQueryTrait {
     44     typedef Element* OutputType;
     45     static const bool shouldOnlyMatchFirstElement = true;
     46     ALWAYS_INLINE static void appendElement(OutputType& output, Element& element)
     47     {
     48         ASSERT(!output);
     49         output = &element;
     50     }
     51 };
     52 
     53 struct AllElementsSelectorQueryTrait {
     54     typedef WillBeHeapVector<RefPtrWillBeMember<Element> > OutputType;
     55     static const bool shouldOnlyMatchFirstElement = false;
     56     ALWAYS_INLINE static void appendElement(OutputType& output, Element& element)
     57     {
     58         output.append(&element);
     59     }
     60 };
     61 
     62 enum ClassElementListBehavior { AllElements, OnlyRoots };
     63 
     64 template <ClassElementListBehavior onlyRoots>
     65 class ClassElementList {
     66     STACK_ALLOCATED();
     67 public:
     68     ClassElementList(ContainerNode& rootNode, const AtomicString& className)
     69         : m_className(className)
     70         , m_rootNode(&rootNode)
     71         , m_currentElement(nextInternal(ElementTraversal::firstWithin(rootNode))) { }
     72 
     73     bool isEmpty() const { return !m_currentElement; }
     74 
     75     Element* next()
     76     {
     77         Element* current = m_currentElement;
     78         ASSERT(current);
     79         if (onlyRoots)
     80             m_currentElement = nextInternal(ElementTraversal::nextSkippingChildren(*m_currentElement, m_rootNode));
     81         else
     82             m_currentElement = nextInternal(ElementTraversal::next(*m_currentElement, m_rootNode));
     83         return current;
     84     }
     85 
     86 private:
     87     Element* nextInternal(Element* element)
     88     {
     89         for (; element; element = ElementTraversal::next(*element, m_rootNode)) {
     90             if (element->hasClass() && element->classNames().contains(m_className))
     91                 return element;
     92         }
     93         return 0;
     94     }
     95 
     96     const AtomicString& m_className;
     97     RawPtrWillBeMember<ContainerNode> m_rootNode;
     98     RawPtrWillBeMember<Element> m_currentElement;
     99 };
    100 
    101 void SelectorDataList::initialize(const CSSSelectorList& selectorList)
    102 {
    103     ASSERT(m_selectors.isEmpty());
    104 
    105     unsigned selectorCount = 0;
    106     for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(*selector))
    107         selectorCount++;
    108 
    109     m_crossesTreeBoundary = false;
    110     m_selectors.reserveInitialCapacity(selectorCount);
    111     unsigned index = 0;
    112     for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(*selector), ++index) {
    113         m_selectors.uncheckedAppend(selector);
    114         m_crossesTreeBoundary |= selectorList.selectorCrossesTreeScopes(index);
    115     }
    116 }
    117 
    118 inline bool SelectorDataList::selectorMatches(const CSSSelector& selector, Element& element, const ContainerNode& rootNode) const
    119 {
    120     SelectorChecker selectorChecker(element.document(), SelectorChecker::QueryingRules);
    121     SelectorChecker::SelectorCheckingContext selectorCheckingContext(selector, &element, SelectorChecker::VisitedMatchDisabled);
    122     selectorCheckingContext.scope = !rootNode.isDocumentNode() ? &rootNode : 0;
    123     if (selectorCheckingContext.scope)
    124         selectorCheckingContext.contextFlags = SelectorChecker::ScopeContainsLastMatchedElement;
    125     return selectorChecker.match(selectorCheckingContext, DOMSiblingTraversalStrategy()) == SelectorChecker::SelectorMatches;
    126 }
    127 
    128 bool SelectorDataList::matches(Element& targetElement) const
    129 {
    130     unsigned selectorCount = m_selectors.size();
    131     for (unsigned i = 0; i < selectorCount; ++i) {
    132         if (selectorMatches(*m_selectors[i], targetElement, targetElement))
    133             return true;
    134     }
    135 
    136     return false;
    137 }
    138 
    139 PassRefPtrWillBeRawPtr<StaticElementList> SelectorDataList::queryAll(ContainerNode& rootNode) const
    140 {
    141     WillBeHeapVector<RefPtrWillBeMember<Element> > result;
    142     execute<AllElementsSelectorQueryTrait>(rootNode, result);
    143     return StaticElementList::adopt(result);
    144 }
    145 
    146 PassRefPtrWillBeRawPtr<Element> SelectorDataList::queryFirst(ContainerNode& rootNode) const
    147 {
    148     Element* matchedElement = 0;
    149     execute<SingleElementSelectorQueryTrait>(rootNode, matchedElement);
    150     return matchedElement;
    151 }
    152 
    153 template <typename SelectorQueryTrait>
    154 void SelectorDataList::collectElementsByClassName(ContainerNode& rootNode, const AtomicString& className,  typename SelectorQueryTrait::OutputType& output) const
    155 {
    156     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
    157         if (element->hasClass() && element->classNames().contains(className)) {
    158             SelectorQueryTrait::appendElement(output, *element);
    159             if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
    160                 return;
    161         }
    162     }
    163 }
    164 
    165 template <typename SelectorQueryTrait>
    166 void SelectorDataList::collectElementsByTagName(ContainerNode& rootNode, const QualifiedName& tagName,  typename SelectorQueryTrait::OutputType& output) const
    167 {
    168     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
    169         if (SelectorChecker::tagMatches(*element, tagName)) {
    170             SelectorQueryTrait::appendElement(output, *element);
    171             if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
    172                 return;
    173         }
    174     }
    175 }
    176 
    177 inline bool SelectorDataList::canUseFastQuery(const ContainerNode& rootNode) const
    178 {
    179     return m_selectors.size() == 1 && !m_crossesTreeBoundary && rootNode.inDocument() && !rootNode.document().inQuirksMode();
    180 }
    181 
    182 inline bool ancestorHasClassName(ContainerNode& rootNode, const AtomicString& className)
    183 {
    184     if (!rootNode.isElementNode())
    185         return false;
    186 
    187     for (Element* element = &toElement(rootNode); element; element = element->parentElement()) {
    188         if (element->hasClass() && element->classNames().contains(className))
    189             return true;
    190     }
    191     return false;
    192 }
    193 
    194 
    195 // If returns true, traversalRoots has the elements that may match the selector query.
    196 //
    197 // If returns false, traversalRoots has the rootNode parameter or descendants of rootNode representing
    198 // the subtree for which we can limit the querySelector traversal.
    199 //
    200 // The travseralRoots may be empty, regardless of the returned bool value, if this method finds that the selectors won't
    201 // match any element.
    202 template <typename SelectorQueryTrait>
    203 void SelectorDataList::findTraverseRootsAndExecute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
    204 {
    205     // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches
    206     // we would need to sort the results. For now, just traverse the document in that case.
    207     ASSERT(m_selectors.size() == 1);
    208 
    209     bool isRightmostSelector = true;
    210     bool startFromParent = false;
    211 
    212     for (const CSSSelector* selector = m_selectors[0]; selector; selector = selector->tagHistory()) {
    213         if (selector->match() == CSSSelector::Id && !rootNode.document().containsMultipleElementsWithId(selector->value())) {
    214             Element* element = rootNode.treeScope().getElementById(selector->value());
    215             ContainerNode* adjustedNode = &rootNode;
    216             if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)))
    217                 adjustedNode = element;
    218             else if (!element || isRightmostSelector)
    219                 adjustedNode = 0;
    220             if (isRightmostSelector) {
    221                 executeForTraverseRoot<SelectorQueryTrait>(*m_selectors[0], adjustedNode, MatchesTraverseRoots, rootNode, output);
    222                 return;
    223             }
    224 
    225             if (startFromParent && adjustedNode)
    226                 adjustedNode = adjustedNode->parentNode();
    227 
    228             executeForTraverseRoot<SelectorQueryTrait>(*m_selectors[0], adjustedNode, DoesNotMatchTraverseRoots, rootNode, output);
    229             return;
    230         }
    231 
    232         // If we have both CSSSelector::Id and CSSSelector::Class at the same time, we should use Id
    233         // to find traverse root.
    234         if (!SelectorQueryTrait::shouldOnlyMatchFirstElement && !startFromParent && selector->match() == CSSSelector::Class) {
    235             if (isRightmostSelector) {
    236                 ClassElementList<AllElements> traverseRoots(rootNode, selector->value());
    237                 executeForTraverseRoots<SelectorQueryTrait>(*m_selectors[0], traverseRoots, MatchesTraverseRoots, rootNode, output);
    238                 return;
    239             }
    240             // Since there exists some ancestor element which has the class name, we need to see all children of rootNode.
    241             if (ancestorHasClassName(rootNode, selector->value())) {
    242                 executeForTraverseRoot<SelectorQueryTrait>(*m_selectors[0], &rootNode, DoesNotMatchTraverseRoots, rootNode, output);
    243                 return;
    244             }
    245 
    246             ClassElementList<OnlyRoots> traverseRoots(rootNode, selector->value());
    247             executeForTraverseRoots<SelectorQueryTrait>(*m_selectors[0], traverseRoots, DoesNotMatchTraverseRoots, rootNode, output);
    248             return;
    249         }
    250 
    251         if (selector->relation() == CSSSelector::SubSelector)
    252             continue;
    253         isRightmostSelector = false;
    254         if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
    255             startFromParent = true;
    256         else
    257             startFromParent = false;
    258     }
    259 
    260     executeForTraverseRoot<SelectorQueryTrait>(*m_selectors[0], &rootNode, DoesNotMatchTraverseRoots, rootNode, output);
    261 }
    262 
    263 template <typename SelectorQueryTrait>
    264 void SelectorDataList::executeForTraverseRoot(const CSSSelector& selector, ContainerNode* traverseRoot, MatchTraverseRootState matchTraverseRoot, ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
    265 {
    266     if (!traverseRoot)
    267         return;
    268 
    269     if (matchTraverseRoot) {
    270         if (selectorMatches(selector, toElement(*traverseRoot), rootNode))
    271             SelectorQueryTrait::appendElement(output, toElement(*traverseRoot));
    272         return;
    273     }
    274 
    275     for (Element* element = ElementTraversal::firstWithin(*traverseRoot); element; element = ElementTraversal::next(*element, traverseRoot)) {
    276         if (selectorMatches(selector, *element, rootNode)) {
    277             SelectorQueryTrait::appendElement(output, *element);
    278             if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
    279                 return;
    280         }
    281     }
    282 }
    283 
    284 template <typename SelectorQueryTrait, typename SimpleElementListType>
    285 void SelectorDataList::executeForTraverseRoots(const CSSSelector& selector, SimpleElementListType& traverseRoots, MatchTraverseRootState matchTraverseRoots, ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
    286 {
    287     if (traverseRoots.isEmpty())
    288         return;
    289 
    290     if (matchTraverseRoots) {
    291         while (!traverseRoots.isEmpty()) {
    292             Element& element = *traverseRoots.next();
    293             if (selectorMatches(selector, element, rootNode)) {
    294                 SelectorQueryTrait::appendElement(output, element);
    295                 if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
    296                     return;
    297             }
    298         }
    299         return;
    300     }
    301 
    302     while (!traverseRoots.isEmpty()) {
    303         Element& traverseRoot = *traverseRoots.next();
    304         for (Element* element = ElementTraversal::firstWithin(traverseRoot); element; element = ElementTraversal::next(*element, &traverseRoot)) {
    305             if (selectorMatches(selector, *element, rootNode)) {
    306                 SelectorQueryTrait::appendElement(output, *element);
    307                 if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
    308                     return;
    309             }
    310         }
    311     }
    312 }
    313 
    314 template <typename SelectorQueryTrait>
    315 bool SelectorDataList::selectorListMatches(ContainerNode& rootNode, Element& element, typename SelectorQueryTrait::OutputType& output) const
    316 {
    317     for (unsigned i = 0; i < m_selectors.size(); ++i) {
    318         if (selectorMatches(*m_selectors[i], element, rootNode)) {
    319             SelectorQueryTrait::appendElement(output, element);
    320             return true;
    321         }
    322     }
    323     return false;
    324 }
    325 
    326 template <typename SelectorQueryTrait>
    327 void SelectorDataList::executeSlow(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
    328 {
    329     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
    330         if (selectorListMatches<SelectorQueryTrait>(rootNode, *element, output) && SelectorQueryTrait::shouldOnlyMatchFirstElement)
    331             return;
    332     }
    333 }
    334 
    335 // FIXME: Move the following helper functions, authorShadowRootOf, firstWithinTraversingShadowTree,
    336 // nextTraversingShadowTree to the best place, e.g. NodeTraversal.
    337 static ShadowRoot* authorShadowRootOf(const ContainerNode& node)
    338 {
    339     if (!node.isElementNode() || !isShadowHost(&node))
    340         return 0;
    341 
    342     ElementShadow* shadow = toElement(node).shadow();
    343     ASSERT(shadow);
    344     for (ShadowRoot* shadowRoot = shadow->oldestShadowRoot(); shadowRoot; shadowRoot = shadowRoot->youngerShadowRoot()) {
    345         if (shadowRoot->type() == ShadowRoot::AuthorShadowRoot)
    346             return shadowRoot;
    347     }
    348     return 0;
    349 }
    350 
    351 static ContainerNode* firstWithinTraversingShadowTree(const ContainerNode& rootNode)
    352 {
    353     if (ShadowRoot* shadowRoot = authorShadowRootOf(rootNode))
    354         return shadowRoot;
    355     return ElementTraversal::firstWithin(rootNode);
    356 }
    357 
    358 static ContainerNode* nextTraversingShadowTree(const ContainerNode& node, const ContainerNode* rootNode)
    359 {
    360     if (ShadowRoot* shadowRoot = authorShadowRootOf(node))
    361         return shadowRoot;
    362 
    363     const ContainerNode* current = &node;
    364     while (current) {
    365         if (Element* next = ElementTraversal::next(*current, rootNode))
    366             return next;
    367 
    368         if (!current->isInShadowTree())
    369             return 0;
    370 
    371         ShadowRoot* shadowRoot = current->containingShadowRoot();
    372         if (shadowRoot == rootNode)
    373             return 0;
    374         if (ShadowRoot* youngerShadowRoot = shadowRoot->youngerShadowRoot()) {
    375             // Should not obtain any elements in user-agent shadow root.
    376             ASSERT(youngerShadowRoot->type() == ShadowRoot::AuthorShadowRoot);
    377             return youngerShadowRoot;
    378         }
    379 
    380         current = shadowRoot->host();
    381     }
    382     return 0;
    383 }
    384 
    385 template <typename SelectorQueryTrait>
    386 void SelectorDataList::executeSlowTraversingShadowTree(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
    387 {
    388     for (ContainerNode* node = firstWithinTraversingShadowTree(rootNode); node; node = nextTraversingShadowTree(*node, &rootNode)) {
    389         if (!node->isElementNode())
    390             continue;
    391         Element* element = toElement(node);
    392         if (selectorListMatches<SelectorQueryTrait>(rootNode, *element, output) && SelectorQueryTrait::shouldOnlyMatchFirstElement)
    393             return;
    394     }
    395 }
    396 
    397 const CSSSelector* SelectorDataList::selectorForIdLookup(const CSSSelector& firstSelector) const
    398 {
    399     for (const CSSSelector* selector = &firstSelector; selector; selector = selector->tagHistory()) {
    400         if (selector->match() == CSSSelector::Id)
    401             return selector;
    402         if (selector->relation() != CSSSelector::SubSelector)
    403             break;
    404     }
    405     return 0;
    406 }
    407 
    408 template <typename SelectorQueryTrait>
    409 void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
    410 {
    411     if (!canUseFastQuery(rootNode)) {
    412         if (m_crossesTreeBoundary) {
    413             rootNode.document().updateDistributionForNodeIfNeeded(&rootNode);
    414             executeSlowTraversingShadowTree<SelectorQueryTrait>(rootNode, output);
    415         } else {
    416             executeSlow<SelectorQueryTrait>(rootNode, output);
    417         }
    418         return;
    419     }
    420 
    421     ASSERT(m_selectors.size() == 1);
    422 
    423     const CSSSelector& selector = *m_selectors[0];
    424     const CSSSelector& firstSelector = selector;
    425 
    426     // Fast path for querySelector*('#id'), querySelector*('tag#id').
    427     if (const CSSSelector* idSelector = selectorForIdLookup(firstSelector)) {
    428         const AtomicString& idToMatch = idSelector->value();
    429         if (rootNode.treeScope().containsMultipleElementsWithId(idToMatch)) {
    430             const WillBeHeapVector<RawPtrWillBeMember<Element> >& elements = rootNode.treeScope().getAllElementsById(idToMatch);
    431             size_t count = elements.size();
    432             for (size_t i = 0; i < count; ++i) {
    433                 Element& element = *elements[i];
    434                 if (!(isTreeScopeRoot(rootNode) || element.isDescendantOf(&rootNode)))
    435                     continue;
    436                 if (selectorMatches(selector, element, rootNode)) {
    437                     SelectorQueryTrait::appendElement(output, element);
    438                     if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
    439                         return;
    440                 }
    441             }
    442             return;
    443         }
    444         Element* element = rootNode.treeScope().getElementById(idToMatch);
    445         if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)))
    446             return;
    447         if (selectorMatches(selector, *element, rootNode))
    448             SelectorQueryTrait::appendElement(output, *element);
    449         return;
    450     }
    451 
    452     if (!firstSelector.tagHistory()) {
    453         // Fast path for querySelector*('.foo'), and querySelector*('div').
    454         switch (firstSelector.match()) {
    455         case CSSSelector::Class:
    456             collectElementsByClassName<SelectorQueryTrait>(rootNode, firstSelector.value(), output);
    457             return;
    458         case CSSSelector::Tag:
    459             collectElementsByTagName<SelectorQueryTrait>(rootNode, firstSelector.tagQName(), output);
    460             return;
    461         default:
    462             break; // If we need another fast path, add here.
    463         }
    464     }
    465 
    466     findTraverseRootsAndExecute<SelectorQueryTrait>(rootNode, output);
    467 }
    468 
    469 PassOwnPtr<SelectorQuery> SelectorQuery::adopt(CSSSelectorList& selectorList)
    470 {
    471     return adoptPtr(new SelectorQuery(selectorList));
    472 }
    473 
    474 SelectorQuery::SelectorQuery(CSSSelectorList& selectorList)
    475 {
    476     m_selectorList.adopt(selectorList);
    477     m_selectors.initialize(m_selectorList);
    478 }
    479 
    480 bool SelectorQuery::matches(Element& element) const
    481 {
    482     return m_selectors.matches(element);
    483 }
    484 
    485 PassRefPtrWillBeRawPtr<StaticElementList> SelectorQuery::queryAll(ContainerNode& rootNode) const
    486 {
    487     return m_selectors.queryAll(rootNode);
    488 }
    489 
    490 PassRefPtrWillBeRawPtr<Element> SelectorQuery::queryFirst(ContainerNode& rootNode) const
    491 {
    492     return m_selectors.queryFirst(rootNode);
    493 }
    494 
    495 SelectorQuery* SelectorQueryCache::add(const AtomicString& selectors, const Document& document, ExceptionState& exceptionState)
    496 {
    497     HashMap<AtomicString, OwnPtr<SelectorQuery> >::iterator it = m_entries.find(selectors);
    498     if (it != m_entries.end())
    499         return it->value.get();
    500 
    501     CSSParser parser(CSSParserContext(document, 0));
    502     CSSSelectorList selectorList;
    503     parser.parseSelector(selectors, selectorList);
    504 
    505     if (!selectorList.first()) {
    506         exceptionState.throwDOMException(SyntaxError, "'" + selectors + "' is not a valid selector.");
    507         return 0;
    508     }
    509 
    510     // throw a NamespaceError if the selector includes any namespace prefixes.
    511     if (selectorList.selectorsNeedNamespaceResolution()) {
    512         exceptionState.throwDOMException(NamespaceError, "'" + selectors + "' contains namespaces, which are not supported.");
    513         return 0;
    514     }
    515 
    516     const unsigned maximumSelectorQueryCacheSize = 256;
    517     if (m_entries.size() == maximumSelectorQueryCacheSize)
    518         m_entries.remove(m_entries.begin());
    519 
    520     return m_entries.add(selectors, SelectorQuery::adopt(selectorList)).storedValue->value.get();
    521 }
    522 
    523 void SelectorQueryCache::invalidate()
    524 {
    525     m_entries.clear();
    526 }
    527 
    528 }
    529