Index: Source/core/css/AffectedByFocusTest.cpp |
diff --git a/Source/core/css/AffectedByFocusTest.cpp b/Source/core/css/AffectedByFocusTest.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c12f07c7a8fb0f0aa1329f1e09967e3c068ced9d |
--- /dev/null |
+++ b/Source/core/css/AffectedByFocusTest.cpp |
@@ -0,0 +1,253 @@ |
+// 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 "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 childrenAffectedBy; |
+ }; |
+ |
+ 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].childrenAffectedBy, elm->childrenAffectedByFocus()); |
+ } |
+ |
+ 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 childrenAffectedByFocus. |
+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 childrenAffectedByFocus. |
+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 childrenAffectedByFocus. |
+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 childrenAffectedByFocus. |
+// Note that currently ":focus:not(body)" does not do the same. Then the :focus is |
+// checked and the childrenAffectedByFocus 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 childrenAffectedByFocus |
+// 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, ChildrenAffectedByFocusUpdate) |
+{ |
+ // 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); |
+} |
+ |
+} // namespace |