blob: 86652324ef7bb3f40fafc01bade619e0d5d889c8 [file] [log] [blame]
/*
* Copyright (C) 2008, 2012 Apple Inc. All rights reserved.
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/css/css_selector_list.h"
#include <memory>
#include <vector>
#include "third_party/blink/renderer/core/css/parser/css_parser_selector.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace {
// CSSSelector is one of the top types that consume renderer memory,
// so instead of using the |WTF_HEAP_PROFILER_TYPE_NAME| macro in the
// allocations below, pass this type name constant to allow profiling
// in official builds.
const char kCSSSelectorTypeName[] = "blink::CSSSelector";
}
namespace blink {
CSSSelectorList CSSSelectorList::Copy() const {
CSSSelectorList list;
unsigned length = this->ComputeLength();
list.selector_array_ =
reinterpret_cast<CSSSelector*>(WTF::Partitions::FastMalloc(
WTF::Partitions::ComputeAllocationSize(length, sizeof(CSSSelector)),
kCSSSelectorTypeName));
for (unsigned i = 0; i < length; ++i)
new (&list.selector_array_[i]) CSSSelector(selector_array_[i]);
return list;
}
CSSSelectorList CSSSelectorList::ConcatenateListExpansion(
const CSSSelectorList& expanded,
const CSSSelectorList& original) {
unsigned expanded_length = expanded.ComputeLength();
unsigned original_length = original.ComputeLength();
unsigned total_length = expanded_length + original_length;
CSSSelectorList list;
list.selector_array_ = reinterpret_cast<CSSSelector*>(
WTF::Partitions::FastMalloc(WTF::Partitions::ComputeAllocationSize(
total_length, sizeof(CSSSelector)),
kCSSSelectorTypeName));
unsigned list_index = 0;
for (unsigned i = 0; i < expanded_length; ++i) {
new (&list.selector_array_[list_index])
CSSSelector(expanded.selector_array_[i]);
++list_index;
}
DCHECK(list.selector_array_[list_index - 1].IsLastInOriginalList());
DCHECK(list.selector_array_[list_index - 1].IsLastInSelectorList());
list.selector_array_[list_index - 1].SetLastInSelectorList(false);
for (unsigned i = 0; i < original_length; ++i) {
new (&list.selector_array_[list_index])
CSSSelector(original.selector_array_[i]);
++list_index;
}
DCHECK(list.selector_array_[list_index - 1].IsLastInOriginalList());
DCHECK(list.selector_array_[list_index - 1].IsLastInSelectorList());
return list;
}
std::vector<const CSSSelector*> SelectorBoundaries(
const CSSSelectorList& list) {
std::vector<const CSSSelector*> result;
for (const CSSSelector* s = list.First(); s; s = list.Next(*s)) {
result.push_back(s);
}
result.push_back(list.First() + list.ComputeLength());
return result;
}
void AddToList(CSSSelector*& destination,
const CSSSelector* begin,
const CSSSelector* end) {
for (const CSSSelector* current = begin; current != end; ++current) {
new (destination) CSSSelector(*current);
destination->SetLastInSelectorList(false);
destination->SetLastInOriginalList(false);
destination++;
}
}
void AddToList(CSSSelector*& destination,
const CSSSelector* begin,
const CSSSelector* end,
const CSSSelector* selector_to_expand) {
for (const CSSSelector* current = begin; current != end; ++current) {
new (destination) CSSSelector(*current);
DCHECK_EQ(current + 1 == end, current->IsLastInTagHistory());
if (current->IsLastInTagHistory()) {
destination->SetRelation(selector_to_expand->Relation());
if (!selector_to_expand->IsLastInTagHistory())
destination->SetLastInTagHistory(false);
}
if (selector_to_expand->GetPseudoType() == CSSSelector::kPseudoWhere ||
selector_to_expand->IgnoreSpecificity())
destination->SetIgnoreSpecificity(true);
destination->SetLastInSelectorList(false);
destination->SetLastInOriginalList(false);
destination++;
}
}
CSSSelectorList CSSSelectorList::ExpandedFirstPseudoClass() const {
DCHECK(this->RequiresExpansion());
unsigned original_length = this->ComputeLength();
std::vector<const CSSSelector*> selector_boundaries =
SelectorBoundaries(*this);
size_t begin = 0;
CSSSelectorList transformed = this->Copy();
while (!selector_boundaries[begin]->HasPseudoIs() &&
!selector_boundaries[begin]->HasPseudoWhere())
++begin;
const CSSSelector* selector_to_expand_begin = selector_boundaries[begin];
const CSSSelector* selector_to_expand_end = selector_boundaries[begin + 1];
unsigned selector_to_expand_length =
static_cast<unsigned>(selector_to_expand_end - selector_to_expand_begin);
const CSSSelector* simple_selector = selector_to_expand_begin;
while (simple_selector->GetPseudoType() != CSSSelector::kPseudoIs &&
simple_selector->GetPseudoType() != CSSSelector::kPseudoWhere) {
simple_selector = simple_selector->TagHistory();
}
unsigned inner_selector_length =
simple_selector->SelectorList()->ComputeLength();
std::vector<const CSSSelector*> selector_arg_boundaries =
SelectorBoundaries(*simple_selector->SelectorList());
wtf_size_t num_args =
SafeCast<wtf_size_t>(selector_arg_boundaries.size()) - 1;
unsigned other_selectors_length = original_length - selector_to_expand_length;
wtf_size_t expanded_selector_list_length =
(selector_to_expand_length - 1) * num_args + inner_selector_length +
other_selectors_length;
// Do not perform expansion if the selector list size is too large to create
// RuleData
if (expanded_selector_list_length > 8192)
return CSSSelectorList();
CSSSelectorList list;
list.selector_array_ =
reinterpret_cast<CSSSelector*>(WTF::Partitions::FastMalloc(
WTF::Partitions::ComputeAllocationSize(expanded_selector_list_length,
sizeof(CSSSelector)),
kCSSSelectorTypeName));
CSSSelector* destination = list.selector_array_;
AddToList(destination, selector_boundaries[0], selector_to_expand_begin);
for (wtf_size_t i = 0; i < num_args; ++i) {
AddToList(destination, selector_to_expand_begin, simple_selector);
AddToList(destination, selector_arg_boundaries[i],
selector_arg_boundaries[i + 1], simple_selector);
AddToList(destination, simple_selector + 1, selector_to_expand_end);
}
AddToList(destination, selector_to_expand_end, selector_boundaries.back());
DCHECK(destination == list.selector_array_ + expanded_selector_list_length);
list.selector_array_[expanded_selector_list_length - 1].SetLastInOriginalList(
true);
list.selector_array_[expanded_selector_list_length - 1].SetLastInSelectorList(
true);
return list;
}
CSSSelectorList CSSSelectorList::TransformForListExpansion() {
DCHECK_GT(this->ComputeLength(), 0u);
DCHECK(
this->selector_array_[this->ComputeLength() - 1].IsLastInOriginalList());
DCHECK(this->RequiresExpansion());
// Append the expanded form of matches to the original selector list
CSSSelectorList transformed = this->Copy();
do {
transformed = transformed.ExpandedFirstPseudoClass();
} while (transformed.RequiresExpansion());
if (transformed.ComputeLength() == 0)
return CSSSelectorList();
return CSSSelectorList::ConcatenateListExpansion(transformed, *this);
}
bool CSSSelectorList::HasPseudoIs() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoIs())
return true;
}
return false;
}
bool CSSSelectorList::HasPseudoWhere() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoWhere())
return true;
}
return false;
}
bool CSSSelectorList::RequiresExpansion() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoIs() || s->HasPseudoWhere())
return true;
}
return false;
}
CSSSelectorList CSSSelectorList::AdoptSelectorVector(
Vector<std::unique_ptr<CSSParserSelector>>& selector_vector) {
size_t flattened_size = 0;
for (wtf_size_t i = 0; i < selector_vector.size(); ++i) {
for (CSSParserSelector* selector = selector_vector[i].get(); selector;
selector = selector->TagHistory())
++flattened_size;
}
DCHECK(flattened_size);
CSSSelectorList list;
list.selector_array_ = reinterpret_cast<CSSSelector*>(
WTF::Partitions::FastMalloc(WTF::Partitions::ComputeAllocationSize(
flattened_size, sizeof(CSSSelector)),
kCSSSelectorTypeName));
wtf_size_t array_index = 0;
for (wtf_size_t i = 0; i < selector_vector.size(); ++i) {
CSSParserSelector* current = selector_vector[i].get();
while (current) {
// Move item from the parser selector vector into selector_array_ without
// invoking destructor (Ugh.)
CSSSelector* current_selector = current->ReleaseSelector().release();
memcpy(&list.selector_array_[array_index], current_selector,
sizeof(CSSSelector));
WTF::Partitions::FastFree(current_selector);
current = current->TagHistory();
DCHECK(!list.selector_array_[array_index].IsLastInSelectorList());
if (current)
list.selector_array_[array_index].SetLastInTagHistory(false);
++array_index;
}
DCHECK(list.selector_array_[array_index - 1].IsLastInTagHistory());
}
DCHECK_EQ(flattened_size, array_index);
list.selector_array_[array_index - 1].SetLastInSelectorList(true);
list.selector_array_[array_index - 1].SetLastInOriginalList(true);
selector_vector.clear();
return list;
}
const CSSSelector* CSSSelectorList::FirstForCSSOM() const {
const CSSSelector* s = this->First();
if (!s)
return nullptr;
while (this->Next(*s))
s = this->Next(*s);
if (this->NextInFullList(*s))
return this->NextInFullList(*s);
return this->First();
}
unsigned CSSSelectorList::ComputeLength() const {
if (!selector_array_)
return 0;
CSSSelector* current = selector_array_;
while (!current->IsLastInSelectorList())
++current;
return static_cast<unsigned>(current - selector_array_) + 1;
}
void CSSSelectorList::DeleteSelectors() {
DCHECK(selector_array_);
bool finished = false;
for (CSSSelector* s = selector_array_; !finished; ++s) {
finished = s->IsLastInSelectorList();
s->~CSSSelector();
}
WTF::Partitions::FastFree(selector_array_);
}
String CSSSelectorList::SelectorsText() const {
StringBuilder result;
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s != FirstForCSSOM())
result.Append(", ");
result.Append(s->SelectorText());
}
return result.ToString();
}
} // namespace blink