Home | History | Annotate | Download | only in css
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "config.h"
      6 #include "core/HTMLNames.h"
      7 #include "core/dom/Element.h"
      8 #include "core/dom/ElementTraversal.h"
      9 #include "core/dom/NodeRenderStyle.h"
     10 #include "core/frame/FrameView.h"
     11 #include "core/html/HTMLDocument.h"
     12 #include "core/html/HTMLElement.h"
     13 #include "core/testing/DummyPageHolder.h"
     14 #include <gtest/gtest.h>
     15 
     16 using namespace WebCore;
     17 using namespace HTMLNames;
     18 
     19 namespace {
     20 
     21 class AffectedByFocusTest : public ::testing::Test {
     22 
     23 protected:
     24 
     25     struct ElementResult {
     26         const WebCore::QualifiedName tag;
     27         bool affectedBy;
     28         bool childrenOrSiblingsAffectedBy;
     29     };
     30 
     31     virtual void SetUp() OVERRIDE;
     32 
     33     HTMLDocument& document() const { return *m_document; }
     34 
     35     void setHtmlInnerHTML(const char* htmlContent);
     36 
     37     void checkElements(ElementResult expected[], unsigned expectedCount) const;
     38 
     39 private:
     40     OwnPtr<DummyPageHolder> m_dummyPageHolder;
     41 
     42     HTMLDocument* m_document;
     43 };
     44 
     45 void AffectedByFocusTest::SetUp()
     46 {
     47     m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
     48     m_document = toHTMLDocument(&m_dummyPageHolder->document());
     49     ASSERT(m_document);
     50 }
     51 
     52 void AffectedByFocusTest::setHtmlInnerHTML(const char* htmlContent)
     53 {
     54     document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent), ASSERT_NO_EXCEPTION);
     55     document().view()->updateLayoutAndStyleIfNeededRecursive();
     56 }
     57 
     58 void AffectedByFocusTest::checkElements(ElementResult expected[], unsigned expectedCount) const
     59 {
     60     unsigned i = 0;
     61     Element* elm = document().body();
     62 
     63     for (; elm && i < expectedCount; elm = ElementTraversal::next(*elm), ++i) {
     64         ASSERT_TRUE(elm->hasTagName(expected[i].tag));
     65         ASSERT(elm->renderStyle());
     66         ASSERT_EQ(expected[i].affectedBy, elm->renderStyle()->affectedByFocus());
     67         ASSERT_EQ(expected[i].childrenOrSiblingsAffectedBy, elm->childrenOrSiblingsAffectedByFocus());
     68     }
     69 
     70     ASSERT(!elm && i == expectedCount);
     71 }
     72 
     73 // A global :focus rule in html.css currently causes every single element to be
     74 // affectedByFocus. Check that all elements in a document with no :focus rules
     75 // gets the affectedByFocus set on RenderStyle and not childrenOrSiblingsAffectedByFocus.
     76 TEST_F(AffectedByFocusTest, UAUniversalFocusRule)
     77 {
     78     ElementResult expected[] = {
     79         { bodyTag, true, false },
     80         { divTag, true, false },
     81         { divTag, true, false },
     82         { divTag, true, false },
     83         { spanTag, true, false }
     84     };
     85 
     86     setHtmlInnerHTML("<body>"
     87         "<div><div></div></div>"
     88         "<div><span></span></div>"
     89         "</body>");
     90 
     91     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
     92 }
     93 
     94 // ":focus div" will mark ascendants of all divs with childrenOrSiblingsAffectedByFocus.
     95 TEST_F(AffectedByFocusTest, FocusedAscendant)
     96 {
     97     ElementResult expected[] = {
     98         { bodyTag, true, true },
     99         { divTag, true, true },
    100         { divTag, true, false },
    101         { divTag, true, false },
    102         { spanTag, true, false }
    103     };
    104 
    105     setHtmlInnerHTML("<head>"
    106         "<style>:focus div { background-color: pink }</style>"
    107         "</head>"
    108         "<body>"
    109         "<div><div></div></div>"
    110         "<div><span></span></div>"
    111         "</body>");
    112 
    113     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
    114 }
    115 
    116 // "body:focus div" will mark the body element with childrenOrSiblingsAffectedByFocus.
    117 TEST_F(AffectedByFocusTest, FocusedAscendantWithType)
    118 {
    119     ElementResult expected[] = {
    120         { bodyTag, true, true },
    121         { divTag, true, false },
    122         { divTag, true, false },
    123         { divTag, true, false },
    124         { spanTag, true, false }
    125     };
    126 
    127     setHtmlInnerHTML("<head>"
    128         "<style>body:focus div { background-color: pink }</style>"
    129         "</head>"
    130         "<body>"
    131         "<div><div></div></div>"
    132         "<div><span></span></div>"
    133         "</body>");
    134 
    135     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
    136 }
    137 
    138 // ":not(body):focus div" should not mark the body element with childrenOrSiblingsAffectedByFocus.
    139 // Note that currently ":focus:not(body)" does not do the same. Then the :focus is
    140 // checked and the childrenOrSiblingsAffectedByFocus flag set before the negated type selector
    141 // is found.
    142 TEST_F(AffectedByFocusTest, FocusedAscendantWithNegatedType)
    143 {
    144     ElementResult expected[] = {
    145         { bodyTag, true, false },
    146         { divTag, true, true },
    147         { divTag, true, false },
    148         { divTag, true, false },
    149         { spanTag, true, false }
    150     };
    151 
    152     setHtmlInnerHTML("<head>"
    153         "<style>:not(body):focus div { background-color: pink }</style>"
    154         "</head>"
    155         "<body>"
    156         "<div><div></div></div>"
    157         "<div><span></span></div>"
    158         "</body>");
    159 
    160     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
    161 }
    162 
    163 // Checking current behavior for ":focus + div", but this is a BUG or at best
    164 // sub-optimal. The focused element will also in this case get childrenOrSiblingsAffectedByFocus
    165 // even if it's really a sibling. Effectively, the whole sub-tree of the focused
    166 // element will have styles recalculated even though none of the children are
    167 // affected. There are other mechanisms that makes sure the sibling also gets its
    168 // styles recalculated.
    169 TEST_F(AffectedByFocusTest, FocusedSibling)
    170 {
    171     ElementResult expected[] = {
    172         { bodyTag, true, false },
    173         { divTag, true, true },
    174         { spanTag, true, false },
    175         { divTag, true, false }
    176     };
    177 
    178     setHtmlInnerHTML("<head>"
    179         "<style>:focus + div { background-color: pink }</style>"
    180         "</head>"
    181         "<body>"
    182         "<div>"
    183         "  <span></span>"
    184         "</div>"
    185         "<div></div>"
    186         "</body>");
    187 
    188     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
    189 }
    190 
    191 TEST_F(AffectedByFocusTest, AffectedByFocusUpdate)
    192 {
    193     // Check that when focussing the outer div in the document below, you only
    194     // get a single element style recalc.
    195 
    196     setHtmlInnerHTML("<style>:focus { border: 1px solid lime; }</style>"
    197         "<div id=d tabIndex=1>"
    198         "<div></div>"
    199         "<div></div>"
    200         "<div></div>"
    201         "<div></div>"
    202         "<div></div>"
    203         "<div></div>"
    204         "<div></div>"
    205         "<div></div>"
    206         "<div></div>"
    207         "<div></div>"
    208         "</div>");
    209 
    210     document().view()->updateLayoutAndStyleIfNeededRecursive();
    211 
    212     unsigned startCount = document().styleEngine()->resolverAccessCount();
    213 
    214     document().getElementById("d")->focus();
    215     document().view()->updateLayoutAndStyleIfNeededRecursive();
    216 
    217     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
    218 
    219     ASSERT_EQ(1U, accessCount);
    220 }
    221 
    222 TEST_F(AffectedByFocusTest, ChildrenOrSiblingsAffectedByFocusUpdate)
    223 {
    224     // Check that when focussing the outer div in the document below, you get a
    225     // style recalc for the whole subtree.
    226 
    227     setHtmlInnerHTML("<style>:focus div { border: 1px solid lime; }</style>"
    228         "<div id=d tabIndex=1>"
    229         "<div></div>"
    230         "<div></div>"
    231         "<div></div>"
    232         "<div></div>"
    233         "<div></div>"
    234         "<div></div>"
    235         "<div></div>"
    236         "<div></div>"
    237         "<div></div>"
    238         "<div></div>"
    239         "</div>");
    240 
    241     document().view()->updateLayoutAndStyleIfNeededRecursive();
    242 
    243     unsigned startCount = document().styleEngine()->resolverAccessCount();
    244 
    245     document().getElementById("d")->focus();
    246     document().view()->updateLayoutAndStyleIfNeededRecursive();
    247 
    248     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
    249 
    250     ASSERT_EQ(11U, accessCount);
    251 }
    252 
    253 TEST_F(AffectedByFocusTest, InvalidationSetFocusUpdate)
    254 {
    255     // Check that when focussing the outer div in the document below, you get a
    256     // style recalc for the outer div and the class=a div only.
    257 
    258     setHtmlInnerHTML("<style>:focus .a { border: 1px solid lime; }</style>"
    259         "<div id=d tabIndex=1>"
    260         "<div></div>"
    261         "<div></div>"
    262         "<div></div>"
    263         "<div></div>"
    264         "<div></div>"
    265         "<div></div>"
    266         "<div></div>"
    267         "<div></div>"
    268         "<div></div>"
    269         "<div class='a'></div>"
    270         "</div>");
    271 
    272     document().view()->updateLayoutAndStyleIfNeededRecursive();
    273 
    274     unsigned startCount = document().styleEngine()->resolverAccessCount();
    275 
    276     document().getElementById("d")->focus();
    277     document().view()->updateLayoutAndStyleIfNeededRecursive();
    278 
    279     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
    280 
    281     ASSERT_EQ(2U, accessCount);
    282 }
    283 
    284 TEST_F(AffectedByFocusTest, NoInvalidationSetFocusUpdate)
    285 {
    286     // Check that when focussing the outer div in the document below, you get a
    287     // style recalc for the outer div only. The invalidation set for :focus will
    288     // include 'a', but the id=d div should be affectedByFocus, not childrenOrSiblingsAffectedByFocus.
    289 
    290     setHtmlInnerHTML("<style>#nomatch:focus .a { border: 1px solid lime; }</style>"
    291         "<div id=d tabIndex=1>"
    292         "<div></div>"
    293         "<div></div>"
    294         "<div></div>"
    295         "<div></div>"
    296         "<div></div>"
    297         "<div></div>"
    298         "<div></div>"
    299         "<div></div>"
    300         "<div></div>"
    301         "<div class='a'></div>"
    302         "</div>");
    303 
    304     document().view()->updateLayoutAndStyleIfNeededRecursive();
    305 
    306     unsigned startCount = document().styleEngine()->resolverAccessCount();
    307 
    308     document().getElementById("d")->focus();
    309     document().view()->updateLayoutAndStyleIfNeededRecursive();
    310 
    311     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
    312 
    313     ASSERT_EQ(1U, accessCount);
    314 }
    315 
    316 } // namespace
    317