blob: d4c7d8cc521741b29d3bd126a16218331c6e6462 [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/rule_matching.h"
#include <algorithm>
#include <vector>
#include "cobalt/css_parser/parser.h"
#include "cobalt/cssom/css_rule_list.h"
#include "cobalt/cssom/css_style_rule.h"
#include "cobalt/cssom/css_style_sheet.h"
#include "cobalt/dom/document.h"
#include "cobalt/dom/dom_stat_tracker.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/html_collection.h"
#include "cobalt/dom/html_element.h"
#include "cobalt/dom/html_element_context.h"
#include "cobalt/dom/node.h"
#include "cobalt/dom/node_descendants_iterator.h"
#include "cobalt/dom/node_list.h"
#include "cobalt/dom/testing/stub_window.h"
#include "cobalt/dom_parser/parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
namespace dom {
class RuleMatchingTest : public ::testing::Test {
protected:
RuleMatchingTest()
: css_parser_(css_parser::Parser::Create()),
dom_parser_(new dom_parser::Parser()),
dom_stat_tracker_(new DomStatTracker("RuleMatchingTest")),
html_element_context_(NULL, css_parser_.get(), dom_parser_.get(), NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, dom_stat_tracker_.get(), "",
base::kApplicationStateStarted),
document_(new Document(&html_element_context_)),
root_(document_->CreateElement("html")->AsHTMLElement()),
head_(document_->CreateElement("head")->AsHTMLElement()),
body_(document_->CreateElement("body")->AsHTMLElement()) {
root_->AppendChild(head_);
root_->AppendChild(body_);
document_->AppendChild(root_);
}
~RuleMatchingTest() OVERRIDE {}
void UpdateAllMatchingRules();
scoped_refptr<cssom::CSSStyleSheet> GetDocumentStyleSheet(
unsigned int index) {
return document_->style_sheets()->Item(index)->AsCSSStyleSheet();
}
scoped_ptr<css_parser::Parser> css_parser_;
scoped_ptr<dom_parser::Parser> dom_parser_;
scoped_ptr<DomStatTracker> dom_stat_tracker_;
HTMLElementContext html_element_context_;
scoped_refptr<Document> document_;
scoped_refptr<HTMLElement> root_;
scoped_refptr<HTMLElement> head_;
scoped_refptr<HTMLElement> body_;
};
void RuleMatchingTest::UpdateAllMatchingRules() {
document_->UpdateSelectorTree();
NodeDescendantsIterator iterator(document_);
Node* child = iterator.First();
while (child) {
if (child->AsElement()) {
DCHECK(child->AsElement()->AsHTMLElement());
UpdateMatchingRules(child->AsElement()->AsHTMLElement());
}
child = iterator.Next();
}
}
// For each test, we go through the following process:
// 1. Generate rules in the style sheet from the CSS code.
// 2. Generate DOM elements from the HTML code.
// 3. Get the matching rules and check the result.
// * should match <div/>.
TEST_F(RuleMatchingTest, UniversalSelectorMatch) {
head_->set_inner_html("<style>* {}</style>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div should match <div/>.
TEST_F(RuleMatchingTest, TypeSelectorMatch) {
head_->set_inner_html("<style>div {}</style>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// [attr] should match <div attr/>.
TEST_F(RuleMatchingTest, AttributeSelectorMatchNoValue) {
head_->set_inner_html("<style>[attr] {}</style>");
body_->set_inner_html("<div attr/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// [attr=value] should match <div attr="value"/>.
TEST_F(RuleMatchingTest, AttributeSelectorMatchEquals) {
head_->set_inner_html("<style>[attr=value] {}</style>");
body_->set_inner_html("<div attr=\"value\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// [attr="value"] should match <div attr="value"/>.
TEST_F(RuleMatchingTest, AttributeSelectorMatchEqualsWithQuote) {
head_->set_inner_html("<style>[attr=\"value\"] {}</style>");
body_->set_inner_html("<div attr=\"value\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// [attr=value] should not match <div attr/>.
TEST_F(RuleMatchingTest, AttributeSelectorNoMatchEquals) {
head_->set_inner_html("<style>[attr=value] {}</style>");
body_->set_inner_html("<div attr/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// .my-class should match <div class="my-class"/>.
TEST_F(RuleMatchingTest, ClassSelectorMatch) {
head_->set_inner_html("<style>.my-class {}</style>");
body_->set_inner_html("<div class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// #div1 should match <div id="div1"/>.
TEST_F(RuleMatchingTest, IdSelectorMatch) {
head_->set_inner_html("<style>#div1 {}</style>");
body_->set_inner_html("<div id=\"div1\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// :empty should match <div></div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassMatch) {
head_->set_inner_html("<style>:empty {}</style>");
body_->set_inner_html("<div></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// :empty should match <div><!--comment--></div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassShouldMatchCommentOnly) {
head_->set_inner_html("<style>:empty {}</style>");
body_->set_inner_html("<div><!--comment--></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// :empty shouldn't match <div> </div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassShouldMatchTextOnly) {
head_->set_inner_html("<style>:empty {}</style>");
body_->set_inner_html("<div> </div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// div:focus should match focused div.
TEST_F(RuleMatchingTest, FocusPseudoClassMatch) {
// Give the document browsing context which is needed for focus to work.
testing::StubWindow window;
document_->set_window(window.window());
// Give the document initial computed style.
document_->SetViewport(math::Size(320, 240));
head_->set_inner_html("<style>:focus {}</style>");
body_->set_inner_html("<div tabIndex=\"-1\"/>");
body_->first_element_child()->AsHTMLElement()->Focus();
// This is required because RunFocusingSteps() earlies out as a result of the
// document not having a browsing context.
root_->InvalidateMatchingRulesRecursively();
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div:focus shouldn't match unfocused div.
TEST_F(RuleMatchingTest, FocusPseudoClassNoMatch) {
// Give the document browsing context which is needed for focus to work.
testing::StubWindow window;
document_->set_window(window.window());
// Give the document initial computed style.
document_->SetViewport(math::Size(320, 240));
head_->set_inner_html("<style>:focus {}</style>");
body_->set_inner_html("<div tabIndex=\"-1\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// :not(.my-class) should match <div/>.
TEST_F(RuleMatchingTest, NotPseudoClassMatch) {
head_->set_inner_html("<style>:not(.my-class) {}</style>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// :not(.my-class) shouldn't match <div class="my-class"/>.
TEST_F(RuleMatchingTest, NotPseudoClassNoMatch) {
head_->set_inner_html("<style>:not(.my-class) {}</style>");
body_->set_inner_html("<div class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// *:after should create and match the after pseudo element of all elements.
TEST_F(RuleMatchingTest, AfterPseudoElementMatchGlobal) {
head_->set_inner_html("<style>*:after {}</style>");
body_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
UpdateAllMatchingRules();
HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
matching_rules =
html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
html_element = body_->last_element_child()->AsHTMLElement();
matching_rules = html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
matching_rules =
html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div:after should create and match the after pseudo element of <div/>.
TEST_F(RuleMatchingTest, AfterPseudoElementSelectorMatch) {
head_->set_inner_html("<style>div:after {}</style>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
matching_rules =
html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div:after shouldn't create the after pseudo element of <span/>.
TEST_F(RuleMatchingTest, AfterPseudoElementSelectorNoMatch) {
head_->set_inner_html("<style>div:after {}</style>");
body_->set_inner_html("<span/>");
UpdateAllMatchingRules();
HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
EXPECT_FALSE(html_element->pseudo_element(kAfterPseudoElementType));
}
// *:before should create and match the before pseudo element of all elements.
TEST_F(RuleMatchingTest, BeforePseudoElementMatchGlobal) {
head_->set_inner_html("<style>*:before {}</style>");
body_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
UpdateAllMatchingRules();
HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
matching_rules =
html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
html_element = body_->last_element_child()->AsHTMLElement();
matching_rules = html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
matching_rules =
html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div:before should create and match the before pseudo element of <div/>.
TEST_F(RuleMatchingTest, BeforePseudoElementSelectorMatch) {
head_->set_inner_html("<style>div:before {}</style>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
matching_rules =
html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div:before shouldn't create the before pseudo element of <span/>.
TEST_F(RuleMatchingTest, BeforePseudoElementSelectorNoMatch) {
head_->set_inner_html("<style>div:before {}</style>");
body_->set_inner_html("<span/>");
UpdateAllMatchingRules();
HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
EXPECT_FALSE(html_element->pseudo_element(kBeforePseudoElementType));
}
// :empty shouldn't match (the outer) <div><div></div></div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassNotMatchElement) {
head_->set_inner_html("<style>:empty {}</style>");
body_->set_inner_html("<div><div></div></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// div.my-class should match <div class="my-class"/>.
TEST_F(RuleMatchingTest, CompoundSelectorMatch) {
head_->set_inner_html("<style>div.my-class {}</style>");
body_->set_inner_html("<div class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// div.my-class shouldn't match <div/> or <span class="my-class"/>.
TEST_F(RuleMatchingTest, CompoundSelectorNoMatch) {
head_->set_inner_html("<style>div.my-class {}</style>");
body_->set_inner_html("<div/><span class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
matching_rules =
body_->last_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// "div span" should match inner span in <div><span><span></span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorDescendantCombinatorMatch) {
head_->set_inner_html("<style>div span {}</style>");
body_->set_inner_html("<div><span><span></span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()
->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// "span span" shouldn't match span in <div><span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorDescendantCombinatorNoMatch) {
head_->set_inner_html("<style>span span {}</style>");
body_->set_inner_html("<div><span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// "div > span" should match span in <div><span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorChildCombinatorMatch) {
head_->set_inner_html("<style>div > span {}</style>");
body_->set_inner_html("<div><span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// "div > span" shouldn't match inner span in
// <div><span><span></span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorChildCombinatorNoMatch) {
head_->set_inner_html("<style>div > span {}</style>");
body_->set_inner_html("<div><span><span></span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()
->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// "span + span" should match second span in <span/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorMatch) {
head_->set_inner_html("<style>span + span {}</style>");
body_->set_inner_html("<span/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->last_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// "span + span" shouldm't match first span in <span/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorNoMatch) {
head_->set_inner_html("<style>span + span {}</style>");
body_->set_inner_html("<span/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// "div ~ span" should match second span in <div/><span/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorFollowingSiblingCombinatorMatch) {
head_->set_inner_html("<style>div ~ span {}</style>");
body_->set_inner_html("<div/><span/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->last_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
// "span ~ span" shouldn't match span in <div/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorFollowingSiblingCombinatorNoMatch) {
head_->set_inner_html("<style>span ~ span {}</style>");
body_->set_inner_html("<div/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->last_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
TEST_F(RuleMatchingTest, SelectorListMatchShouldContainAllMatches) {
head_->set_inner_html(
"<style>.first-class, #my-id, .first-class.second-class {}</style>");
body_->set_inner_html(
"<div class=\"first-class second-class\" id=\"my-id\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(3, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[1].first);
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[2].first);
std::vector<cssom::Specificity> vs;
vs.push_back((*matching_rules)[0].second.specificity());
vs.push_back((*matching_rules)[1].second.specificity());
vs.push_back((*matching_rules)[2].second.specificity());
sort(vs.begin(), vs.end());
EXPECT_EQ(vs[0], cssom::Specificity(0, 1, 0));
EXPECT_EQ(vs[1], cssom::Specificity(0, 2, 0));
EXPECT_EQ(vs[2], cssom::Specificity(1, 0, 0));
}
// A complex example using several combinators.
TEST_F(RuleMatchingTest, ComplexSelectorCombinedMatch) {
head_->set_inner_html(
"<style>div ~ span + div ~ div + div > div + div {}</style>");
body_->set_inner_html(
"<div/><span/><span/><div/><span/><div/>"
"<div><div/><div/></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->last_element_child()
->last_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
TEST_F(RuleMatchingTest, QuerySelectorShouldReturnFirstMatch) {
body_->set_inner_html(
"<div id='div1'>"
" <div id='div2'/>"
" <div id='div3'/>"
"</div>");
scoped_refptr<Element> div1 =
QuerySelector(document_, "div", css_parser_.get());
ASSERT_TRUE(div1);
EXPECT_EQ("div1", div1->id());
EXPECT_FALSE(QuerySelector(document_, "span", css_parser_.get()));
}
TEST_F(RuleMatchingTest, QuerySelectorShouldLimitResultInSubtree) {
body_->set_inner_html(
"<div id='div1'>"
" <div id='div2'/>"
" <div id='div3'/>"
"</div>");
scoped_refptr<Element> div1 =
QuerySelector(document_, "div", css_parser_.get());
ASSERT_TRUE(div1);
EXPECT_EQ("div1", div1->id());
scoped_refptr<Element> div2 = QuerySelector(div1, "div", css_parser_.get());
ASSERT_TRUE(div2);
EXPECT_EQ("div2", div2->id());
}
TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorOutsideSubtree) {
body_->set_inner_html(
"<div class='out'>"
" <div id='div1'>"
" <span/>"
" </div>"
"</div>");
scoped_refptr<Element> div1 =
QuerySelector(document_, "#div1", css_parser_.get());
ASSERT_TRUE(div1);
EXPECT_EQ("div1", div1->id());
scoped_refptr<Element> span =
QuerySelector(div1, ".out span", css_parser_.get());
EXPECT_TRUE(span);
}
TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorsRecursively) {
body_->set_inner_html(
"<div/>"
"<div/>"
"<p/>"
"<div/>"
"<span/>");
scoped_refptr<Element> span =
QuerySelector(document_, "div + div ~ span", css_parser_.get());
EXPECT_TRUE(span);
}
TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorsCombined) {
body_->set_inner_html(
"<div/>"
"<span/>"
"<span/>"
"<div/>"
"<span/>"
"<div/>"
"<div>"
" <div/>"
" <div/>"
"</div>");
scoped_refptr<Element> div = QuerySelector(
document_, "div ~ span + div ~ div + div > div + div", css_parser_.get());
EXPECT_TRUE(div);
}
TEST_F(RuleMatchingTest, QuerySelectorAllShouldReturnAllMatches) {
body_->set_inner_html(
"<div id='div1'>"
" <div id='div2'/>"
" <div id='div3'/>"
"</div>");
scoped_refptr<NodeList> node_list;
node_list = QuerySelectorAll(document_, "div", css_parser_.get());
ASSERT_EQ(3, node_list->length());
EXPECT_EQ("div1", node_list->Item(0)->AsElement()->id());
EXPECT_EQ("div2", node_list->Item(1)->AsElement()->id());
EXPECT_EQ("div3", node_list->Item(2)->AsElement()->id());
node_list = QuerySelectorAll(document_, "span", css_parser_.get());
EXPECT_EQ(0, node_list->length());
}
TEST_F(RuleMatchingTest, StyleElementRemoval) {
head_->set_inner_html("<style>* {}</style>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
head_->set_inner_html("<style/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
TEST_F(RuleMatchingTest, StyleElementAddition) {
head_->set_inner_html("<style/>");
body_->set_inner_html("<div/>");
UpdateAllMatchingRules();
head_->set_inner_html("<style>* {}</style>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
body_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
}
TEST_F(RuleMatchingTest, StyleElementReorderingOneMatching) {
scoped_refptr<HTMLElement> div1 =
document_->CreateElement("div")->AsHTMLElement();
div1->set_inner_html("<style/>");
scoped_refptr<HTMLElement> div2 =
document_->CreateElement("div")->AsHTMLElement();
div2->set_inner_html("<style>* {}</style>");
body_->set_inner_html("<div/>");
head_->AppendChild(div1);
head_->AppendChild(div2);
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
head_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_NE(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
EXPECT_EQ(GetDocumentStyleSheet(1)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
head_->RemoveChild(div2);
head_->InsertBefore(div2, div1);
UpdateAllMatchingRules();
matching_rules =
head_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
EXPECT_NE(GetDocumentStyleSheet(1)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
TEST_F(RuleMatchingTest, StyleElementReorderingTwoMatching) {
scoped_refptr<HTMLElement> div1 =
document_->CreateElement("div")->AsHTMLElement();
div1->set_inner_html("<style>* {}</style>");
scoped_refptr<HTMLElement> div2 =
document_->CreateElement("div")->AsHTMLElement();
div2->set_inner_html("<style>* {}</style>");
body_->set_inner_html("<div/>");
head_->AppendChild(div1);
head_->AppendChild(div2);
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
head_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(2, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
EXPECT_NE(GetDocumentStyleSheet(1)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
head_->RemoveChild(div2);
head_->InsertBefore(div2, div1);
UpdateAllMatchingRules();
matching_rules =
head_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(2, matching_rules->size());
EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
EXPECT_NE(GetDocumentStyleSheet(1)->css_rules_same_origin()->Item(0),
(*matching_rules)[0].first);
}
} // namespace dom
} // namespace cobalt