diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/core/css/AffectedByFocusTest.cpp')
-rw-r--r-- | chromium/third_party/WebKit/Source/core/css/AffectedByFocusTest.cpp | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/core/css/AffectedByFocusTest.cpp b/chromium/third_party/WebKit/Source/core/css/AffectedByFocusTest.cpp new file mode 100644 index 00000000000..c2c4467ba29 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/css/AffectedByFocusTest.cpp @@ -0,0 +1,316 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "core/HTMLNames.h" +#include "core/dom/Element.h" +#include "core/dom/ElementTraversal.h" +#include "core/dom/NodeRenderStyle.h" +#include "core/frame/FrameView.h" +#include "core/html/HTMLDocument.h" +#include "core/html/HTMLElement.h" +#include "core/testing/DummyPageHolder.h" +#include <gtest/gtest.h> + +using namespace WebCore; +using namespace HTMLNames; + +namespace { + +class AffectedByFocusTest : public ::testing::Test { + +protected: + + struct ElementResult { + const WebCore::QualifiedName tag; + bool affectedBy; + bool childrenOrSiblingsAffectedBy; + }; + + virtual void SetUp() OVERRIDE; + + HTMLDocument& document() const { return *m_document; } + + void setHtmlInnerHTML(const char* htmlContent); + + void checkElements(ElementResult expected[], unsigned expectedCount) const; + +private: + OwnPtr<DummyPageHolder> m_dummyPageHolder; + + HTMLDocument* m_document; +}; + +void AffectedByFocusTest::SetUp() +{ + m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600)); + m_document = toHTMLDocument(&m_dummyPageHolder->document()); + ASSERT(m_document); +} + +void AffectedByFocusTest::setHtmlInnerHTML(const char* htmlContent) +{ + document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent), ASSERT_NO_EXCEPTION); + document().view()->updateLayoutAndStyleIfNeededRecursive(); +} + +void AffectedByFocusTest::checkElements(ElementResult expected[], unsigned expectedCount) const +{ + unsigned i = 0; + Element* elm = document().body(); + + for (; elm && i < expectedCount; elm = ElementTraversal::next(*elm), ++i) { + ASSERT_TRUE(elm->hasTagName(expected[i].tag)); + ASSERT(elm->renderStyle()); + ASSERT_EQ(expected[i].affectedBy, elm->renderStyle()->affectedByFocus()); + ASSERT_EQ(expected[i].childrenOrSiblingsAffectedBy, elm->childrenOrSiblingsAffectedByFocus()); + } + + ASSERT(!elm && i == expectedCount); +} + +// A global :focus rule in html.css currently causes every single element to be +// affectedByFocus. Check that all elements in a document with no :focus rules +// gets the affectedByFocus set on RenderStyle and not childrenOrSiblingsAffectedByFocus. +TEST_F(AffectedByFocusTest, UAUniversalFocusRule) +{ + ElementResult expected[] = { + { bodyTag, true, false }, + { divTag, true, false }, + { divTag, true, false }, + { divTag, true, false }, + { spanTag, true, false } + }; + + setHtmlInnerHTML("<body>" + "<div><div></div></div>" + "<div><span></span></div>" + "</body>"); + + checkElements(expected, sizeof(expected) / sizeof(ElementResult)); +} + +// ":focus div" will mark ascendants of all divs with childrenOrSiblingsAffectedByFocus. +TEST_F(AffectedByFocusTest, FocusedAscendant) +{ + ElementResult expected[] = { + { bodyTag, true, true }, + { divTag, true, true }, + { divTag, true, false }, + { divTag, true, false }, + { spanTag, true, false } + }; + + setHtmlInnerHTML("<head>" + "<style>:focus div { background-color: pink }</style>" + "</head>" + "<body>" + "<div><div></div></div>" + "<div><span></span></div>" + "</body>"); + + checkElements(expected, sizeof(expected) / sizeof(ElementResult)); +} + +// "body:focus div" will mark the body element with childrenOrSiblingsAffectedByFocus. +TEST_F(AffectedByFocusTest, FocusedAscendantWithType) +{ + ElementResult expected[] = { + { bodyTag, true, true }, + { divTag, true, false }, + { divTag, true, false }, + { divTag, true, false }, + { spanTag, true, false } + }; + + setHtmlInnerHTML("<head>" + "<style>body:focus div { background-color: pink }</style>" + "</head>" + "<body>" + "<div><div></div></div>" + "<div><span></span></div>" + "</body>"); + + checkElements(expected, sizeof(expected) / sizeof(ElementResult)); +} + +// ":not(body):focus div" should not mark the body element with childrenOrSiblingsAffectedByFocus. +// Note that currently ":focus:not(body)" does not do the same. Then the :focus is +// checked and the childrenOrSiblingsAffectedByFocus flag set before the negated type selector +// is found. +TEST_F(AffectedByFocusTest, FocusedAscendantWithNegatedType) +{ + ElementResult expected[] = { + { bodyTag, true, false }, + { divTag, true, true }, + { divTag, true, false }, + { divTag, true, false }, + { spanTag, true, false } + }; + + setHtmlInnerHTML("<head>" + "<style>:not(body):focus div { background-color: pink }</style>" + "</head>" + "<body>" + "<div><div></div></div>" + "<div><span></span></div>" + "</body>"); + + checkElements(expected, sizeof(expected) / sizeof(ElementResult)); +} + +// Checking current behavior for ":focus + div", but this is a BUG or at best +// sub-optimal. The focused element will also in this case get childrenOrSiblingsAffectedByFocus +// even if it's really a sibling. Effectively, the whole sub-tree of the focused +// element will have styles recalculated even though none of the children are +// affected. There are other mechanisms that makes sure the sibling also gets its +// styles recalculated. +TEST_F(AffectedByFocusTest, FocusedSibling) +{ + ElementResult expected[] = { + { bodyTag, true, false }, + { divTag, true, true }, + { spanTag, true, false }, + { divTag, true, false } + }; + + setHtmlInnerHTML("<head>" + "<style>:focus + div { background-color: pink }</style>" + "</head>" + "<body>" + "<div>" + " <span></span>" + "</div>" + "<div></div>" + "</body>"); + + checkElements(expected, sizeof(expected) / sizeof(ElementResult)); +} + +TEST_F(AffectedByFocusTest, AffectedByFocusUpdate) +{ + // Check that when focussing the outer div in the document below, you only + // get a single element style recalc. + + setHtmlInnerHTML("<style>:focus { border: 1px solid lime; }</style>" + "<div id=d tabIndex=1>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "</div>"); + + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned startCount = document().styleEngine()->resolverAccessCount(); + + document().getElementById("d")->focus(); + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; + + ASSERT_EQ(1U, accessCount); +} + +TEST_F(AffectedByFocusTest, ChildrenOrSiblingsAffectedByFocusUpdate) +{ + // Check that when focussing the outer div in the document below, you get a + // style recalc for the whole subtree. + + setHtmlInnerHTML("<style>:focus div { border: 1px solid lime; }</style>" + "<div id=d tabIndex=1>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "</div>"); + + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned startCount = document().styleEngine()->resolverAccessCount(); + + document().getElementById("d")->focus(); + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; + + ASSERT_EQ(11U, accessCount); +} + +TEST_F(AffectedByFocusTest, InvalidationSetFocusUpdate) +{ + // Check that when focussing the outer div in the document below, you get a + // style recalc for the outer div and the class=a div only. + + setHtmlInnerHTML("<style>:focus .a { border: 1px solid lime; }</style>" + "<div id=d tabIndex=1>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div class='a'></div>" + "</div>"); + + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned startCount = document().styleEngine()->resolverAccessCount(); + + document().getElementById("d")->focus(); + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; + + ASSERT_EQ(2U, accessCount); +} + +TEST_F(AffectedByFocusTest, NoInvalidationSetFocusUpdate) +{ + // Check that when focussing the outer div in the document below, you get a + // style recalc for the outer div only. The invalidation set for :focus will + // include 'a', but the id=d div should be affectedByFocus, not childrenOrSiblingsAffectedByFocus. + + setHtmlInnerHTML("<style>#nomatch:focus .a { border: 1px solid lime; }</style>" + "<div id=d tabIndex=1>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div></div>" + "<div class='a'></div>" + "</div>"); + + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned startCount = document().styleEngine()->resolverAccessCount(); + + document().getElementById("d")->focus(); + document().view()->updateLayoutAndStyleIfNeededRecursive(); + + unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; + + ASSERT_EQ(1U, accessCount); +} + +} // namespace |