blob: 7d306bfe5bbeb3771388d430a3c56a62ff8c5bb9 [file] [log] [blame]
/*
* Copyright (C) 2010, 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. AND ITS CONTRIBUTORS ``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 ITS 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 "core/inspector/InspectorStyleSheet.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "bindings/core/v8/ScriptRegexp.h"
#include "core/CSSPropertyNames.h"
#include "core/css/CSSImportRule.h"
#include "core/css/CSSKeyframeRule.h"
#include "core/css/CSSKeyframesRule.h"
#include "core/css/CSSMediaRule.h"
#include "core/css/CSSRuleList.h"
#include "core/css/CSSStyleRule.h"
#include "core/css/CSSStyleSheet.h"
#include "core/css/CSSSupportsRule.h"
#include "core/css/StylePropertySet.h"
#include "core/css/StyleRule.h"
#include "core/css/StyleSheetContents.h"
#include "core/css/parser/CSSParser.h"
#include "core/css/parser/CSSParserObserver.h"
#include "core/dom/DOMNodeIds.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/StyleEngine.h"
#include "core/html/HTMLStyleElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/inspector/IdentifiersFactory.h"
#include "core/inspector/InspectorCSSAgent.h"
#include "core/inspector/InspectorNetworkAgent.h"
#include "core/inspector/InspectorResourceContainer.h"
#include "core/svg/SVGStyleElement.h"
#include "wtf/PtrUtil.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/TextPosition.h"
#include <algorithm>
using blink::protocol::Array;
using blink::RuleSourceDataList;
using blink::CSSRuleSourceData;
using blink::CSSStyleSheet;
namespace {
using namespace blink;
static CSSParserContext parserContextForDocument(Document* document) {
return document ? CSSParserContext(*document, nullptr)
: strictCSSParserContext();
}
String findMagicComment(const String& content, const String& name) {
DCHECK(name.find("=") == kNotFound);
unsigned length = content.length();
unsigned nameLength = name.length();
const bool multiline = true;
size_t pos = length;
size_t equalSignPos = 0;
size_t closingCommentPos = 0;
while (true) {
pos = content.reverseFind(name, pos);
if (pos == kNotFound)
return emptyString();
// Check for a /\/[\/*][@#][ \t]/ regexp (length of 4) before found name.
if (pos < 4)
return emptyString();
pos -= 4;
if (content[pos] != '/')
continue;
if ((content[pos + 1] != '/' || multiline) &&
(content[pos + 1] != '*' || !multiline))
continue;
if (content[pos + 2] != '#' && content[pos + 2] != '@')
continue;
if (content[pos + 3] != ' ' && content[pos + 3] != '\t')
continue;
equalSignPos = pos + 4 + nameLength;
if (equalSignPos < length && content[equalSignPos] != '=')
continue;
if (multiline) {
closingCommentPos = content.find("*/", equalSignPos + 1);
if (closingCommentPos == kNotFound)
return emptyString();
}
break;
}
DCHECK(equalSignPos);
DCHECK(!multiline || closingCommentPos);
size_t urlPos = equalSignPos + 1;
String match = multiline
? content.substring(urlPos, closingCommentPos - urlPos)
: content.substring(urlPos);
size_t newLine = match.find("\n");
if (newLine != kNotFound)
match = match.substring(0, newLine);
match = match.stripWhiteSpace();
String disallowedChars("\"' \t");
for (unsigned i = 0; i < match.length(); ++i) {
if (disallowedChars.find(match[i]) != kNotFound)
return emptyString();
}
return match;
}
void getClassNamesFromRule(CSSStyleRule* rule, HashSet<String>& uniqueNames) {
const CSSSelectorList& selectorList = rule->styleRule()->selectorList();
if (!selectorList.isValid())
return;
for (const CSSSelector* subSelector = selectorList.first(); subSelector;
subSelector = CSSSelectorList::next(*subSelector)) {
const CSSSelector* simpleSelector = subSelector;
while (simpleSelector) {
if (simpleSelector->match() == CSSSelector::Class)
uniqueNames.add(simpleSelector->value());
simpleSelector = simpleSelector->tagHistory();
}
}
}
class StyleSheetHandler final : public CSSParserObserver {
public:
StyleSheetHandler(const String& parsedText,
Document* document,
RuleSourceDataList* result)
: m_parsedText(parsedText),
m_document(document),
m_result(result),
m_currentRuleData(nullptr) {
ASSERT(m_result);
}
private:
void startRuleHeader(StyleRule::RuleType, unsigned) override;
void endRuleHeader(unsigned) override;
void observeSelector(unsigned startOffset, unsigned endOffset) override;
void startRuleBody(unsigned) override;
void endRuleBody(unsigned) override;
void observeProperty(unsigned startOffset,
unsigned endOffset,
bool isImportant,
bool isParsed) override;
void observeComment(unsigned startOffset, unsigned endOffset) override;
void addNewRuleToSourceTree(PassRefPtr<CSSRuleSourceData>);
PassRefPtr<CSSRuleSourceData> popRuleData();
template <typename CharacterType>
inline void setRuleHeaderEnd(const CharacterType*, unsigned);
void fixUnparsedPropertyRanges(CSSRuleSourceData*);
const String& m_parsedText;
Member<Document> m_document;
RuleSourceDataList* m_result;
RuleSourceDataList m_currentRuleDataStack;
CSSRuleSourceData* m_currentRuleData;
};
void StyleSheetHandler::startRuleHeader(StyleRule::RuleType type,
unsigned offset) {
// Pop off data for a previous invalid rule.
if (m_currentRuleData)
m_currentRuleDataStack.removeLast();
RefPtr<CSSRuleSourceData> data = CSSRuleSourceData::create(type);
data->ruleHeaderRange.start = offset;
m_currentRuleData = data.get();
m_currentRuleDataStack.append(data.release());
}
template <typename CharacterType>
inline void StyleSheetHandler::setRuleHeaderEnd(const CharacterType* dataStart,
unsigned listEndOffset) {
while (listEndOffset > 1) {
if (isHTMLSpace<CharacterType>(*(dataStart + listEndOffset - 1)))
--listEndOffset;
else
break;
}
m_currentRuleDataStack.last()->ruleHeaderRange.end = listEndOffset;
if (!m_currentRuleDataStack.last()->selectorRanges.isEmpty())
m_currentRuleDataStack.last()->selectorRanges.last().end = listEndOffset;
}
void StyleSheetHandler::endRuleHeader(unsigned offset) {
ASSERT(!m_currentRuleDataStack.isEmpty());
if (m_parsedText.is8Bit())
setRuleHeaderEnd<LChar>(m_parsedText.characters8(), offset);
else
setRuleHeaderEnd<UChar>(m_parsedText.characters16(), offset);
}
void StyleSheetHandler::observeSelector(unsigned startOffset,
unsigned endOffset) {
ASSERT(m_currentRuleDataStack.size());
m_currentRuleDataStack.last()->selectorRanges.append(
SourceRange(startOffset, endOffset));
}
void StyleSheetHandler::startRuleBody(unsigned offset) {
m_currentRuleData = nullptr;
ASSERT(!m_currentRuleDataStack.isEmpty());
if (m_parsedText[offset] == '{')
++offset; // Skip the rule body opening brace.
m_currentRuleDataStack.last()->ruleBodyRange.start = offset;
}
void StyleSheetHandler::endRuleBody(unsigned offset) {
ASSERT(!m_currentRuleDataStack.isEmpty());
m_currentRuleDataStack.last()->ruleBodyRange.end = offset;
RefPtr<CSSRuleSourceData> rule = popRuleData();
fixUnparsedPropertyRanges(rule.get());
addNewRuleToSourceTree(rule.release());
}
void StyleSheetHandler::addNewRuleToSourceTree(
PassRefPtr<CSSRuleSourceData> rule) {
if (m_currentRuleDataStack.isEmpty())
m_result->append(rule);
else
m_currentRuleDataStack.last()->childRules.append(rule);
}
PassRefPtr<CSSRuleSourceData> StyleSheetHandler::popRuleData() {
ASSERT(!m_currentRuleDataStack.isEmpty());
m_currentRuleData = nullptr;
RefPtr<CSSRuleSourceData> data = m_currentRuleDataStack.last().get();
m_currentRuleDataStack.removeLast();
return data.release();
}
template <typename CharacterType>
static inline void fixUnparsedProperties(const CharacterType* characters,
CSSRuleSourceData* ruleData) {
Vector<CSSPropertySourceData>& propertyData =
ruleData->styleSourceData->propertyData;
unsigned size = propertyData.size();
if (!size)
return;
CSSPropertySourceData* nextData = &(propertyData.at(0));
for (unsigned i = 0; i < size; ++i) {
CSSPropertySourceData* currentData = nextData;
nextData = i < size - 1 ? &(propertyData.at(i + 1)) : nullptr;
if (currentData->parsedOk)
continue;
if (currentData->range.end > 0 &&
characters[currentData->range.end - 1] == ';')
continue;
unsigned propertyEnd;
if (!nextData)
propertyEnd = ruleData->ruleBodyRange.end - 1;
else
propertyEnd = nextData->range.start - 1;
while (isHTMLSpace<CharacterType>(characters[propertyEnd]))
--propertyEnd;
// propertyEnd points at the last property text character.
unsigned newPropertyEnd =
propertyEnd + 1; // Exclusive of the last property text character.
if (currentData->range.end != newPropertyEnd) {
currentData->range.end = newPropertyEnd;
unsigned valueStart =
currentData->range.start + currentData->name.length();
while (valueStart < propertyEnd && characters[valueStart] != ':')
++valueStart;
if (valueStart < propertyEnd)
++valueStart; // Shift past the ':'.
while (valueStart < propertyEnd &&
isHTMLSpace<CharacterType>(characters[valueStart]))
++valueStart;
// Need to exclude the trailing ';' from the property value.
currentData->value = String(
characters + valueStart,
propertyEnd - valueStart + (characters[propertyEnd] == ';' ? 0 : 1));
}
}
}
void StyleSheetHandler::fixUnparsedPropertyRanges(CSSRuleSourceData* ruleData) {
if (!ruleData->styleSourceData)
return;
if (m_parsedText.is8Bit()) {
fixUnparsedProperties<LChar>(m_parsedText.characters8(), ruleData);
return;
}
fixUnparsedProperties<UChar>(m_parsedText.characters16(), ruleData);
}
void StyleSheetHandler::observeProperty(unsigned startOffset,
unsigned endOffset,
bool isImportant,
bool isParsed) {
if (m_currentRuleDataStack.isEmpty() ||
!m_currentRuleDataStack.last()->styleSourceData)
return;
ASSERT(endOffset <= m_parsedText.length());
if (endOffset < m_parsedText.length() &&
m_parsedText[endOffset] ==
';') // Include semicolon into the property text.
++endOffset;
ASSERT(startOffset < endOffset);
String propertyString =
m_parsedText.substring(startOffset, endOffset - startOffset)
.stripWhiteSpace();
if (propertyString.endsWith(';'))
propertyString = propertyString.left(propertyString.length() - 1);
size_t colonIndex = propertyString.find(':');
ASSERT(colonIndex != kNotFound);
String name = propertyString.left(colonIndex).stripWhiteSpace();
String value =
propertyString.substring(colonIndex + 1, propertyString.length())
.stripWhiteSpace();
m_currentRuleDataStack.last()->styleSourceData->propertyData.append(
CSSPropertySourceData(name, value, isImportant, false, isParsed,
SourceRange(startOffset, endOffset)));
}
void StyleSheetHandler::observeComment(unsigned startOffset,
unsigned endOffset) {
ASSERT(endOffset <= m_parsedText.length());
if (m_currentRuleDataStack.isEmpty() ||
!m_currentRuleDataStack.last()->ruleHeaderRange.end ||
!m_currentRuleDataStack.last()->styleSourceData)
return;
// The lexer is not inside a property AND it is scanning a declaration-aware
// rule body.
String commentText =
m_parsedText.substring(startOffset, endOffset - startOffset);
ASSERT(commentText.startsWith("/*"));
commentText = commentText.substring(2);
// Require well-formed comments.
if (!commentText.endsWith("*/"))
return;
commentText =
commentText.substring(0, commentText.length() - 2).stripWhiteSpace();
if (commentText.isEmpty())
return;
// FIXME: Use the actual rule type rather than STYLE_RULE?
RuleSourceDataList sourceData;
StyleSheetHandler handler(commentText, m_document, &sourceData);
CSSParser::parseDeclarationListForInspector(
parserContextForDocument(m_document), commentText, handler);
Vector<CSSPropertySourceData>& commentPropertyData =
sourceData.first()->styleSourceData->propertyData;
if (commentPropertyData.size() != 1)
return;
CSSPropertySourceData& propertyData = commentPropertyData.at(0);
bool parsedOk = propertyData.parsedOk ||
propertyData.name.startsWith("-moz-") ||
propertyData.name.startsWith("-o-") ||
propertyData.name.startsWith("-webkit-") ||
propertyData.name.startsWith("-ms-");
if (!parsedOk || propertyData.range.length() != commentText.length())
return;
m_currentRuleDataStack.last()->styleSourceData->propertyData.append(
CSSPropertySourceData(propertyData.name, propertyData.value, false, true,
true, SourceRange(startOffset, endOffset)));
}
bool verifyRuleText(Document* document, const String& ruleText) {
DEFINE_STATIC_LOCAL(String, bogusPropertyName, ("-webkit-boguz-propertee"));
StyleSheetContents* styleSheet =
StyleSheetContents::create(strictCSSParserContext());
RuleSourceDataList sourceData;
String text = ruleText + " div { " + bogusPropertyName + ": none; }";
StyleSheetHandler handler(text, document, &sourceData);
CSSParser::parseSheetForInspector(parserContextForDocument(document),
styleSheet, text, handler);
unsigned ruleCount = sourceData.size();
// Exactly two rules should be parsed.
if (ruleCount != 2)
return false;
// Added rule must be style rule.
if (!sourceData.at(0)->styleSourceData)
return false;
Vector<CSSPropertySourceData>& propertyData =
sourceData.at(1)->styleSourceData->propertyData;
unsigned propertyCount = propertyData.size();
// Exactly one property should be in rule.
if (propertyCount != 1)
return false;
// Check for the property name.
if (propertyData.at(0).name != bogusPropertyName)
return false;
return true;
}
bool verifyStyleText(Document* document, const String& text) {
return verifyRuleText(document, "div {" + text + "}");
}
bool verifyKeyframeKeyText(Document* document, const String& keyText) {
StyleSheetContents* styleSheet =
StyleSheetContents::create(strictCSSParserContext());
RuleSourceDataList sourceData;
String text = "@keyframes boguzAnim { " + keyText +
" { -webkit-boguz-propertee : none; } }";
StyleSheetHandler handler(text, document, &sourceData);
CSSParser::parseSheetForInspector(parserContextForDocument(document),
styleSheet, text, handler);
// Exactly two should be parsed.
unsigned ruleCount = sourceData.size();
if (ruleCount != 2 || sourceData.at(0)->type != StyleRule::Keyframes ||
sourceData.at(1)->type != StyleRule::Keyframe)
return false;
// Exactly one property should be in keyframe rule.
Vector<CSSPropertySourceData>& propertyData =
sourceData.at(1)->styleSourceData->propertyData;
unsigned propertyCount = propertyData.size();
if (propertyCount != 1)
return false;
return true;
}
bool verifySelectorText(Document* document, const String& selectorText) {
DEFINE_STATIC_LOCAL(String, bogusPropertyName, ("-webkit-boguz-propertee"));
StyleSheetContents* styleSheet =
StyleSheetContents::create(strictCSSParserContext());
RuleSourceDataList sourceData;
String text = selectorText + " { " + bogusPropertyName + ": none; }";
StyleSheetHandler handler(text, document, &sourceData);
CSSParser::parseSheetForInspector(parserContextForDocument(document),
styleSheet, text, handler);
// Exactly one rule should be parsed.
unsigned ruleCount = sourceData.size();
if (ruleCount != 1 || sourceData.at(0)->type != StyleRule::Style)
return false;
// Exactly one property should be in style rule.
Vector<CSSPropertySourceData>& propertyData =
sourceData.at(0)->styleSourceData->propertyData;
unsigned propertyCount = propertyData.size();
if (propertyCount != 1)
return false;
// Check for the property name.
if (propertyData.at(0).name != bogusPropertyName)
return false;
return true;
}
bool verifyMediaText(Document* document, const String& mediaText) {
DEFINE_STATIC_LOCAL(String, bogusPropertyName, ("-webkit-boguz-propertee"));
StyleSheetContents* styleSheet =
StyleSheetContents::create(strictCSSParserContext());
RuleSourceDataList sourceData;
String text =
"@media " + mediaText + " { div { " + bogusPropertyName + ": none; } }";
StyleSheetHandler handler(text, document, &sourceData);
CSSParser::parseSheetForInspector(parserContextForDocument(document),
styleSheet, text, handler);
// Exactly one media rule should be parsed.
unsigned ruleCount = sourceData.size();
if (ruleCount != 1 || sourceData.at(0)->type != StyleRule::Media)
return false;
// Media rule should have exactly one style rule child.
RuleSourceDataList& childSourceData = sourceData.at(0)->childRules;
ruleCount = childSourceData.size();
if (ruleCount != 1 || !childSourceData.at(0)->styleSourceData)
return false;
// Exactly one property should be in style rule.
Vector<CSSPropertySourceData>& propertyData =
childSourceData.at(0)->styleSourceData->propertyData;
unsigned propertyCount = propertyData.size();
if (propertyCount != 1)
return false;
// Check for the property name.
if (propertyData.at(0).name != bogusPropertyName)
return false;
return true;
}
void flattenSourceData(const RuleSourceDataList& dataList,
RuleSourceDataList* result) {
for (size_t i = 0; i < dataList.size(); ++i) {
const RefPtr<CSSRuleSourceData>& data = dataList.at(i);
// The result->append()'ed types should be exactly the same as in
// collectFlatRules().
switch (data->type) {
case StyleRule::Style:
case StyleRule::Import:
case StyleRule::Page:
case StyleRule::FontFace:
case StyleRule::Viewport:
case StyleRule::Keyframe:
result->append(data.get());
break;
case StyleRule::Media:
case StyleRule::Supports:
case StyleRule::Keyframes:
result->append(data.get());
flattenSourceData(data->childRules, result);
break;
default:
break;
}
}
}
CSSRuleList* asCSSRuleList(CSSRule* rule) {
if (!rule)
return nullptr;
if (rule->type() == CSSRule::kMediaRule)
return toCSSMediaRule(rule)->cssRules();
if (rule->type() == CSSRule::kSupportsRule)
return toCSSSupportsRule(rule)->cssRules();
if (rule->type() == CSSRule::kKeyframesRule)
return toCSSKeyframesRule(rule)->cssRules();
return nullptr;
}
template <typename RuleList>
void collectFlatRules(RuleList ruleList, CSSRuleVector* result) {
if (!ruleList)
return;
for (unsigned i = 0, size = ruleList->length(); i < size; ++i) {
CSSRule* rule = ruleList->item(i);
// The result->append()'ed types should be exactly the same as in
// flattenSourceData().
switch (rule->type()) {
case CSSRule::kStyleRule:
case CSSRule::kImportRule:
case CSSRule::kCharsetRule:
case CSSRule::kPageRule:
case CSSRule::kFontFaceRule:
case CSSRule::kViewportRule:
case CSSRule::kKeyframeRule:
result->append(rule);
break;
case CSSRule::kMediaRule:
case CSSRule::kSupportsRule:
case CSSRule::kKeyframesRule:
result->append(rule);
collectFlatRules(asCSSRuleList(rule), result);
break;
default:
break;
}
}
}
typedef HashMap<unsigned,
unsigned,
WTF::IntHash<unsigned>,
WTF::UnsignedWithZeroKeyHashTraits<unsigned>>
IndexMap;
void diff(const Vector<String>& listA,
const Vector<String>& listB,
IndexMap* aToB,
IndexMap* bToA) {
// Cut of common prefix.
size_t startOffset = 0;
while (startOffset < listA.size() && startOffset < listB.size()) {
if (listA.at(startOffset) != listB.at(startOffset))
break;
aToB->set(startOffset, startOffset);
bToA->set(startOffset, startOffset);
++startOffset;
}
// Cut of common suffix.
size_t endOffset = 0;
while (endOffset < listA.size() - startOffset &&
endOffset < listB.size() - startOffset) {
size_t indexA = listA.size() - endOffset - 1;
size_t indexB = listB.size() - endOffset - 1;
if (listA.at(indexA) != listB.at(indexB))
break;
aToB->set(indexA, indexB);
bToA->set(indexB, indexA);
++endOffset;
}
int n = listA.size() - startOffset - endOffset;
int m = listB.size() - startOffset - endOffset;
// If we mapped either of arrays, we have no more work to do.
if (n == 0 || m == 0)
return;
int** diff = new int*[n];
int** backtrack = new int*[n];
for (int i = 0; i < n; ++i) {
diff[i] = new int[m];
backtrack[i] = new int[m];
}
// Compute longest common subsequence of two cssom models.
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
int max = 0;
int track = 0;
if (i > 0 && diff[i - 1][j] > max) {
max = diff[i - 1][j];
track = 1;
}
if (j > 0 && diff[i][j - 1] > max) {
max = diff[i][j - 1];
track = 2;
}
if (listA.at(i + startOffset) == listB.at(j + startOffset)) {
int value = i > 0 && j > 0 ? diff[i - 1][j - 1] + 1 : 1;
if (value > max) {
max = value;
track = 3;
}
}
diff[i][j] = max;
backtrack[i][j] = track;
}
}
// Backtrack and add missing mapping.
int i = n - 1, j = m - 1;
while (i >= 0 && j >= 0 && backtrack[i][j]) {
switch (backtrack[i][j]) {
case 1:
i -= 1;
break;
case 2:
j -= 1;
break;
case 3:
aToB->set(i + startOffset, j + startOffset);
bToA->set(j + startOffset, i + startOffset);
i -= 1;
j -= 1;
break;
default:
ASSERT_NOT_REACHED();
}
}
for (int i = 0; i < n; ++i) {
delete[] diff[i];
delete[] backtrack[i];
}
delete[] diff;
delete[] backtrack;
}
String canonicalCSSText(CSSRule* rule) {
if (rule->type() != CSSRule::kStyleRule)
return rule->cssText();
CSSStyleRule* styleRule = toCSSStyleRule(rule);
Vector<String> propertyNames;
CSSStyleDeclaration* style = styleRule->style();
for (unsigned i = 0; i < style->length(); ++i)
propertyNames.append(style->item(i));
std::sort(propertyNames.begin(), propertyNames.end(),
WTF::codePointCompareLessThan);
StringBuilder builder;
builder.append(styleRule->selectorText());
builder.append('{');
for (unsigned i = 0; i < propertyNames.size(); ++i) {
String name = propertyNames.at(i);
builder.append(' ');
builder.append(name);
builder.append(':');
builder.append(style->getPropertyValue(name));
if (!style->getPropertyPriority(name).isEmpty()) {
builder.append(' ');
builder.append(style->getPropertyPriority(name));
}
builder.append(';');
}
builder.append('}');
return builder.toString();
}
} // namespace
namespace blink {
enum MediaListSource {
MediaListSourceLinkedSheet,
MediaListSourceInlineSheet,
MediaListSourceMediaRule,
MediaListSourceImportRule
};
std::unique_ptr<protocol::CSS::SourceRange>
InspectorStyleSheetBase::buildSourceRangeObject(const SourceRange& range) {
const LineEndings* lineEndings = this->lineEndings();
if (!lineEndings)
return nullptr;
TextPosition start =
TextPosition::fromOffsetAndLineEndings(range.start, *lineEndings);
TextPosition end =
TextPosition::fromOffsetAndLineEndings(range.end, *lineEndings);
std::unique_ptr<protocol::CSS::SourceRange> result =
protocol::CSS::SourceRange::create()
.setStartLine(start.m_line.zeroBasedInt())
.setStartColumn(start.m_column.zeroBasedInt())
.setEndLine(end.m_line.zeroBasedInt())
.setEndColumn(end.m_column.zeroBasedInt())
.build();
return result;
}
InspectorStyle* InspectorStyle::create(
CSSStyleDeclaration* style,
PassRefPtr<CSSRuleSourceData> sourceData,
InspectorStyleSheetBase* parentStyleSheet) {
return new InspectorStyle(style, std::move(sourceData), parentStyleSheet);
}
InspectorStyle::InspectorStyle(CSSStyleDeclaration* style,
PassRefPtr<CSSRuleSourceData> sourceData,
InspectorStyleSheetBase* parentStyleSheet)
: m_style(style),
m_sourceData(std::move(sourceData)),
m_parentStyleSheet(parentStyleSheet) {
ASSERT(m_style);
}
InspectorStyle::~InspectorStyle() {}
std::unique_ptr<protocol::CSS::CSSStyle> InspectorStyle::buildObjectForStyle() {
std::unique_ptr<protocol::CSS::CSSStyle> result = styleWithProperties();
if (m_sourceData) {
if (m_parentStyleSheet && !m_parentStyleSheet->id().isEmpty())
result->setStyleSheetId(m_parentStyleSheet->id());
result->setRange(m_parentStyleSheet->buildSourceRangeObject(
m_sourceData->ruleBodyRange));
String sheetText;
bool success = m_parentStyleSheet->getText(&sheetText);
if (success) {
const SourceRange& bodyRange = m_sourceData->ruleBodyRange;
result->setCssText(sheetText.substring(bodyRange.start,
bodyRange.end - bodyRange.start));
}
}
return result;
}
bool InspectorStyle::styleText(String* result) {
if (!m_sourceData)
return false;
return textForRange(m_sourceData->ruleBodyRange, result);
}
bool InspectorStyle::textForRange(const SourceRange& range, String* result) {
String styleSheetText;
bool success = m_parentStyleSheet->getText(&styleSheetText);
if (!success)
return false;
ASSERT(0 <= range.start);
ASSERT(range.start <= range.end);
ASSERT(range.end <= styleSheetText.length());
*result = styleSheetText.substring(range.start, range.end - range.start);
return true;
}
void InspectorStyle::populateAllProperties(
Vector<CSSPropertySourceData>& result) {
HashSet<String> sourcePropertyNames;
if (m_sourceData && m_sourceData->styleSourceData) {
Vector<CSSPropertySourceData>& sourcePropertyData =
m_sourceData->styleSourceData->propertyData;
for (const auto& data : sourcePropertyData) {
result.append(data);
sourcePropertyNames.add(data.name.lower());
}
}
for (int i = 0, size = m_style->length(); i < size; ++i) {
String name = m_style->item(i);
if (!sourcePropertyNames.add(name.lower()).isNewEntry)
continue;
String value = m_style->getPropertyValue(name);
if (value.isEmpty())
continue;
result.append(CSSPropertySourceData(
name, value, !m_style->getPropertyPriority(name).isEmpty(), false, true,
SourceRange()));
}
}
std::unique_ptr<protocol::CSS::CSSStyle> InspectorStyle::styleWithProperties() {
std::unique_ptr<Array<protocol::CSS::CSSProperty>> propertiesObject =
Array<protocol::CSS::CSSProperty>::create();
std::unique_ptr<Array<protocol::CSS::ShorthandEntry>> shorthandEntries =
Array<protocol::CSS::ShorthandEntry>::create();
HashSet<String> foundShorthands;
Vector<CSSPropertySourceData> properties;
populateAllProperties(properties);
for (auto& styleProperty : properties) {
const CSSPropertySourceData& propertyEntry = styleProperty;
const String& name = propertyEntry.name;
std::unique_ptr<protocol::CSS::CSSProperty> property =
protocol::CSS::CSSProperty::create()
.setName(name)
.setValue(propertyEntry.value)
.build();
// Default "parsedOk" == true.
if (!propertyEntry.parsedOk)
property->setParsedOk(false);
String text;
if (styleProperty.range.length() &&
textForRange(styleProperty.range, &text))
property->setText(text);
if (propertyEntry.important)
property->setImportant(true);
if (styleProperty.range.length()) {
property->setRange(
m_parentStyleSheet
? m_parentStyleSheet->buildSourceRangeObject(propertyEntry.range)
: nullptr);
if (!propertyEntry.disabled) {
property->setImplicit(false);
}
property->setDisabled(propertyEntry.disabled);
} else if (!propertyEntry.disabled) {
bool implicit = m_style->isPropertyImplicit(name);
// Default "implicit" == false.
if (implicit)
property->setImplicit(true);
String shorthand = m_style->getPropertyShorthand(name);
if (!shorthand.isEmpty()) {
if (foundShorthands.add(shorthand).isNewEntry) {
std::unique_ptr<protocol::CSS::ShorthandEntry> entry =
protocol::CSS::ShorthandEntry::create()
.setName(shorthand)
.setValue(shorthandValue(shorthand))
.build();
if (!m_style->getPropertyPriority(name).isEmpty())
entry->setImportant(true);
shorthandEntries->addItem(std::move(entry));
}
}
}
propertiesObject->addItem(std::move(property));
}
std::unique_ptr<protocol::CSS::CSSStyle> result =
protocol::CSS::CSSStyle::create()
.setCssProperties(std::move(propertiesObject))
.setShorthandEntries(std::move(shorthandEntries))
.build();
return result;
}
String InspectorStyle::shorthandValue(const String& shorthandProperty) {
StringBuilder builder;
String value = m_style->getPropertyValue(shorthandProperty);
if (value.isEmpty()) {
for (unsigned i = 0; i < m_style->length(); ++i) {
String individualProperty = m_style->item(i);
if (m_style->getPropertyShorthand(individualProperty) !=
shorthandProperty)
continue;
if (m_style->isPropertyImplicit(individualProperty))
continue;
String individualValue = m_style->getPropertyValue(individualProperty);
if (individualValue == "initial")
continue;
if (!builder.isEmpty())
builder.append(' ');
builder.append(individualValue);
}
} else {
builder.append(value);
}
if (!m_style->getPropertyPriority(shorthandProperty).isEmpty())
builder.append(" !important");
return builder.toString();
}
DEFINE_TRACE(InspectorStyle) {
visitor->trace(m_style);
visitor->trace(m_parentStyleSheet);
}
InspectorStyleSheetBase::InspectorStyleSheetBase(Listener* listener)
: m_id(IdentifiersFactory::createIdentifier()),
m_listener(listener),
m_lineEndings(wrapUnique(new LineEndings())) {}
void InspectorStyleSheetBase::onStyleSheetTextChanged() {
m_lineEndings = wrapUnique(new LineEndings());
if (listener())
listener()->styleSheetChanged(this);
}
std::unique_ptr<protocol::CSS::CSSStyle>
InspectorStyleSheetBase::buildObjectForStyle(CSSStyleDeclaration* style) {
return inspectorStyle(style)->buildObjectForStyle();
}
const LineEndings* InspectorStyleSheetBase::lineEndings() {
if (m_lineEndings->size() > 0)
return m_lineEndings.get();
String text;
if (getText(&text))
m_lineEndings = WTF::lineEndings(text);
return m_lineEndings.get();
}
bool InspectorStyleSheetBase::lineNumberAndColumnToOffset(unsigned lineNumber,
unsigned columnNumber,
unsigned* offset) {
const LineEndings* endings = lineEndings();
if (lineNumber >= endings->size())
return false;
unsigned charactersInLine =
lineNumber > 0 ? endings->at(lineNumber) - endings->at(lineNumber - 1) - 1
: endings->at(0);
if (columnNumber > charactersInLine)
return false;
TextPosition position(OrdinalNumber::fromZeroBasedInt(lineNumber),
OrdinalNumber::fromZeroBasedInt(columnNumber));
*offset = position.toOffset(*endings).zeroBasedInt();
return true;
}
InspectorStyleSheet* InspectorStyleSheet::create(
InspectorNetworkAgent* networkAgent,
CSSStyleSheet* pageStyleSheet,
const String& origin,
const String& documentURL,
InspectorStyleSheetBase::Listener* listener,
InspectorResourceContainer* resourceContainer) {
return new InspectorStyleSheet(networkAgent, pageStyleSheet, origin,
documentURL, listener, resourceContainer);
}
InspectorStyleSheet::InspectorStyleSheet(
InspectorNetworkAgent* networkAgent,
CSSStyleSheet* pageStyleSheet,
const String& origin,
const String& documentURL,
InspectorStyleSheetBase::Listener* listener,
InspectorResourceContainer* resourceContainer)
: InspectorStyleSheetBase(listener),
m_resourceContainer(resourceContainer),
m_networkAgent(networkAgent),
m_pageStyleSheet(pageStyleSheet),
m_origin(origin),
m_documentURL(documentURL) {
String text;
bool success = inspectorStyleSheetText(&text);
if (!success)
success = inlineStyleSheetText(&text);
if (!success)
success = resourceStyleSheetText(&text);
if (success)
innerSetText(text, false);
}
InspectorStyleSheet::~InspectorStyleSheet() {}
DEFINE_TRACE(InspectorStyleSheet) {
visitor->trace(m_resourceContainer);
visitor->trace(m_networkAgent);
visitor->trace(m_pageStyleSheet);
visitor->trace(m_cssomFlatRules);
visitor->trace(m_parsedFlatRules);
InspectorStyleSheetBase::trace(visitor);
}
static String styleSheetURL(CSSStyleSheet* pageStyleSheet) {
if (pageStyleSheet && !pageStyleSheet->contents()->baseURL().isEmpty())
return pageStyleSheet->contents()->baseURL().getString();
return emptyString();
}
String InspectorStyleSheet::finalURL() {
String url = styleSheetURL(m_pageStyleSheet.get());
return url.isEmpty() ? m_documentURL : url;
}
bool InspectorStyleSheet::setText(const String& text, ExceptionState&) {
innerSetText(text, true);
m_pageStyleSheet->setText(text);
onStyleSheetTextChanged();
return true;
}
CSSStyleRule* InspectorStyleSheet::setRuleSelector(
const SourceRange& range,
const String& text,
SourceRange* newRange,
String* oldText,
ExceptionState& exceptionState) {
if (!verifySelectorText(m_pageStyleSheet->ownerDocument(), text)) {
exceptionState.throwDOMException(SyntaxError,
"Selector or media text is not valid.");
return nullptr;
}
CSSRuleSourceData* sourceData = findRuleByHeaderRange(range);
if (!sourceData || !sourceData->styleSourceData) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing source range");
return nullptr;
}
CSSRule* rule = ruleForSourceData(sourceData);
if (!rule || !rule->parentStyleSheet() ||
rule->type() != CSSRule::kStyleRule) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing style source range");
return nullptr;
}
CSSStyleRule* styleRule = InspectorCSSAgent::asCSSStyleRule(rule);
styleRule->setSelectorText(text);
replaceText(sourceData->ruleHeaderRange, text, newRange, oldText);
onStyleSheetTextChanged();
return styleRule;
}
CSSKeyframeRule* InspectorStyleSheet::setKeyframeKey(
const SourceRange& range,
const String& text,
SourceRange* newRange,
String* oldText,
ExceptionState& exceptionState) {
if (!verifyKeyframeKeyText(m_pageStyleSheet->ownerDocument(), text)) {
exceptionState.throwDOMException(SyntaxError,
"Keyframe key text is not valid.");
return nullptr;
}
CSSRuleSourceData* sourceData = findRuleByHeaderRange(range);
if (!sourceData || !sourceData->styleSourceData) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing source range");
return nullptr;
}
CSSRule* rule = ruleForSourceData(sourceData);
if (!rule || !rule->parentStyleSheet() ||
rule->type() != CSSRule::kKeyframeRule) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing style source range");
return nullptr;
}
CSSKeyframeRule* keyframeRule = toCSSKeyframeRule(rule);
keyframeRule->setKeyText(text, exceptionState);
replaceText(sourceData->ruleHeaderRange, text, newRange, oldText);
onStyleSheetTextChanged();
return keyframeRule;
}
CSSRule* InspectorStyleSheet::setStyleText(const SourceRange& range,
const String& text,
SourceRange* newRange,
String* oldText,
ExceptionState& exceptionState) {
if (!verifyStyleText(m_pageStyleSheet->ownerDocument(), text)) {
exceptionState.throwDOMException(SyntaxError, "Style text is not valid.");
return nullptr;
}
CSSRuleSourceData* sourceData = findRuleByBodyRange(range);
if (!sourceData || !sourceData->styleSourceData) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing style source range");
return nullptr;
}
CSSRule* rule = ruleForSourceData(sourceData);
if (!rule || !rule->parentStyleSheet() ||
(rule->type() != CSSRule::kStyleRule &&
rule->type() != CSSRule::kKeyframeRule)) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing style source range");
return nullptr;
}
CSSStyleDeclaration* style = nullptr;
if (rule->type() == CSSRule::kStyleRule)
style = toCSSStyleRule(rule)->style();
else if (rule->type() == CSSRule::kKeyframeRule)
style = toCSSKeyframeRule(rule)->style();
style->setCSSText(text, exceptionState);
replaceText(sourceData->ruleBodyRange, text, newRange, oldText);
onStyleSheetTextChanged();
return rule;
}
CSSMediaRule* InspectorStyleSheet::setMediaRuleText(
const SourceRange& range,
const String& text,
SourceRange* newRange,
String* oldText,
ExceptionState& exceptionState) {
if (!verifyMediaText(m_pageStyleSheet->ownerDocument(), text)) {
exceptionState.throwDOMException(SyntaxError,
"Selector or media text is not valid.");
return nullptr;
}
CSSRuleSourceData* sourceData = findRuleByHeaderRange(range);
if (!sourceData || !sourceData->mediaSourceData) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing source range");
return nullptr;
}
CSSRule* rule = ruleForSourceData(sourceData);
if (!rule || !rule->parentStyleSheet() ||
rule->type() != CSSRule::kMediaRule) {
exceptionState.throwDOMException(
NotFoundError, "Source range didn't match existing style source range");
return nullptr;
}
CSSMediaRule* mediaRule = InspectorCSSAgent::asCSSMediaRule(rule);
mediaRule->media()->setMediaText(text);
replaceText(sourceData->ruleHeaderRange, text, newRange, oldText);
onStyleSheetTextChanged();
return mediaRule;
}
CSSRuleSourceData* InspectorStyleSheet::ruleSourceDataAfterSourceRange(
const SourceRange& sourceRange) {
ASSERT(m_sourceData);
unsigned index = 0;
for (; index < m_sourceData->size(); ++index) {
CSSRuleSourceData* sd = m_sourceData->at(index).get();
if (sd->ruleHeaderRange.start >= sourceRange.end)
break;
}
return index < m_sourceData->size() ? m_sourceData->at(index).get() : nullptr;
}
CSSStyleRule* InspectorStyleSheet::insertCSSOMRuleInStyleSheet(
CSSRule* insertBefore,
const String& ruleText,
ExceptionState& exceptionState) {
unsigned index = 0;
for (; index < m_pageStyleSheet->length(); ++index) {
CSSRule* rule = m_pageStyleSheet->item(index);
if (rule == insertBefore)
break;
}
m_pageStyleSheet->insertRule(ruleText, index, exceptionState);
CSSRule* rule = m_pageStyleSheet->item(index);
CSSStyleRule* styleRule = InspectorCSSAgent::asCSSStyleRule(rule);
if (!styleRule) {
m_pageStyleSheet->deleteRule(index, ASSERT_NO_EXCEPTION);
exceptionState.throwDOMException(
SyntaxError,
"The rule '" + ruleText + "' could not be added in style sheet.");
return nullptr;
}
return styleRule;
}
CSSStyleRule* InspectorStyleSheet::insertCSSOMRuleInMediaRule(
CSSMediaRule* mediaRule,
CSSRule* insertBefore,
const String& ruleText,
ExceptionState& exceptionState) {
unsigned index = 0;
for (; index < mediaRule->length(); ++index) {
CSSRule* rule = mediaRule->item(index);
if (rule == insertBefore)
break;
}
mediaRule->insertRule(ruleText, index, exceptionState);
CSSRule* rule = mediaRule->item(index);
CSSStyleRule* styleRule = InspectorCSSAgent::asCSSStyleRule(rule);
if (!styleRule) {
mediaRule->deleteRule(index, ASSERT_NO_EXCEPTION);
exceptionState.throwDOMException(
SyntaxError,
"The rule '" + ruleText + "' could not be added in media rule.");
return nullptr;
}
return styleRule;
}
CSSStyleRule* InspectorStyleSheet::insertCSSOMRuleBySourceRange(
const SourceRange& sourceRange,
const String& ruleText,
ExceptionState& exceptionState) {
ASSERT(m_sourceData);
CSSRuleSourceData* containingRuleSourceData = nullptr;
for (size_t i = 0; i < m_sourceData->size(); ++i) {
CSSRuleSourceData* ruleSourceData = m_sourceData->at(i).get();
if (ruleSourceData->ruleHeaderRange.start < sourceRange.start &&
sourceRange.start < ruleSourceData->ruleBodyRange.start) {
exceptionState.throwDOMException(
NotFoundError, "Cannot insert rule inside rule selector.");
return nullptr;
}
if (sourceRange.start < ruleSourceData->ruleBodyRange.start ||
ruleSourceData->ruleBodyRange.end < sourceRange.start)
continue;
if (!containingRuleSourceData ||
containingRuleSourceData->ruleBodyRange.length() >
ruleSourceData->ruleBodyRange.length())
containingRuleSourceData = ruleSourceData;
}
CSSRuleSourceData* insertBefore = ruleSourceDataAfterSourceRange(sourceRange);
CSSRule* insertBeforeRule = ruleForSourceData(insertBefore);
if (!containingRuleSourceData)
return insertCSSOMRuleInStyleSheet(insertBeforeRule, ruleText,
exceptionState);
CSSRule* rule = ruleForSourceData(containingRuleSourceData);
if (!rule || rule->type() != CSSRule::kMediaRule) {
exceptionState.throwDOMException(NotFoundError,
"Cannot insert rule in non-media rule.");
return nullptr;
}
return insertCSSOMRuleInMediaRule(toCSSMediaRule(rule), insertBeforeRule,
ruleText, exceptionState);
}
CSSStyleRule* InspectorStyleSheet::addRule(const String& ruleText,
const SourceRange& location,
SourceRange* addedRange,
ExceptionState& exceptionState) {
if (location.start != location.end) {
exceptionState.throwDOMException(NotFoundError,
"Source range must be collapsed.");
return nullptr;
}
if (!verifyRuleText(m_pageStyleSheet->ownerDocument(), ruleText)) {
exceptionState.throwDOMException(SyntaxError, "Rule text is not valid.");
return nullptr;
}
if (!m_sourceData) {
exceptionState.throwDOMException(NotFoundError, "Style is read-only.");
return nullptr;
}
CSSStyleRule* styleRule =
insertCSSOMRuleBySourceRange(location, ruleText, exceptionState);
if (exceptionState.hadException())
return nullptr;
replaceText(location, ruleText, addedRange, nullptr);
onStyleSheetTextChanged();
return styleRule;
}
bool InspectorStyleSheet::deleteRule(const SourceRange& range,
ExceptionState& exceptionState) {
if (!m_sourceData) {
exceptionState.throwDOMException(NotFoundError, "Style is read-only.");
return false;
}
// Find index of CSSRule that entirely belongs to the range.
CSSRuleSourceData* foundData = nullptr;
for (size_t i = 0; i < m_sourceData->size(); ++i) {
CSSRuleSourceData* ruleSourceData = m_sourceData->at(i).get();
unsigned ruleStart = ruleSourceData->ruleHeaderRange.start;
unsigned ruleEnd = ruleSourceData->ruleBodyRange.end + 1;
bool startBelongs = ruleStart >= range.start && ruleStart < range.end;
bool endBelongs = ruleEnd > range.start && ruleEnd <= range.end;
if (startBelongs != endBelongs)
break;
if (!startBelongs)
continue;
if (!foundData ||
foundData->ruleBodyRange.length() >
ruleSourceData->ruleBodyRange.length())
foundData = ruleSourceData;
}
CSSRule* rule = ruleForSourceData(foundData);
if (!rule) {
exceptionState.throwDOMException(
NotFoundError, "No style rule could be found in given range.");
return false;
}
CSSStyleSheet* styleSheet = rule->parentStyleSheet();
if (!styleSheet) {
exceptionState.throwDOMException(NotFoundError,
"No parent stylesheet could be found.");
return false;
}
CSSRule* parentRule = rule->parentRule();
if (parentRule) {
if (parentRule->type() != CSSRule::kMediaRule) {
exceptionState.throwDOMException(
NotFoundError, "Cannot remove rule from non-media rule.");
return false;
}
CSSMediaRule* parentMediaRule = toCSSMediaRule(parentRule);
size_t index = 0;
while (index < parentMediaRule->length() &&
parentMediaRule->item(index) != rule)
++index;
ASSERT(index < parentMediaRule->length());
parentMediaRule->deleteRule(index, exceptionState);
} else {
size_t index = 0;
while (index < styleSheet->length() && styleSheet->item(index) != rule)
++index;
ASSERT(index < styleSheet->length());
styleSheet->deleteRule(index, exceptionState);
}
// |rule| MAY NOT be addressed after this line!
if (exceptionState.hadException())
return false;
replaceText(range, "", nullptr, nullptr);
onStyleSheetTextChanged();
return true;
}
std::unique_ptr<protocol::Array<String>>
InspectorStyleSheet::collectClassNames() {
HashSet<String> uniqueNames;
std::unique_ptr<protocol::Array<String>> result =
protocol::Array<String>::create();
for (size_t i = 0; i < m_parsedFlatRules.size(); ++i) {
if (m_parsedFlatRules.at(i)->type() == CSSRule::kStyleRule)
getClassNamesFromRule(toCSSStyleRule(m_parsedFlatRules.at(i)),
uniqueNames);
}
for (const String& className : uniqueNames)
result->addItem(className);
return result;
}
void InspectorStyleSheet::replaceText(const SourceRange& range,
const String& text,
SourceRange* newRange,
String* oldText) {
String sheetText = m_text;
if (oldText)
*oldText = sheetText.substring(range.start, range.length());
sheetText.replace(range.start, range.length(), text);
if (newRange)
*newRange = SourceRange(range.start, range.start + text.length());
innerSetText(sheetText, true);
}
void InspectorStyleSheet::innerSetText(const String& text,
bool markAsLocallyModified) {
RuleSourceDataList ruleTree;
StyleSheetContents* styleSheet =
StyleSheetContents::create(m_pageStyleSheet->contents()->parserContext());
StyleSheetHandler handler(text, m_pageStyleSheet->ownerDocument(), &ruleTree);
CSSParser::parseSheetForInspector(
m_pageStyleSheet->contents()->parserContext(), styleSheet, text, handler);
CSSStyleSheet* sourceDataSheet = nullptr;
if (toCSSImportRule(m_pageStyleSheet->ownerRule()))
sourceDataSheet = CSSStyleSheet::create(
styleSheet, toCSSImportRule(m_pageStyleSheet->ownerRule()));
else
sourceDataSheet =
CSSStyleSheet::create(styleSheet, *m_pageStyleSheet->ownerNode());
m_parsedFlatRules.clear();
collectFlatRules(sourceDataSheet, &m_parsedFlatRules);
m_sourceData = wrapUnique(new RuleSourceDataList());
flattenSourceData(ruleTree, m_sourceData.get());
m_text = text;
if (markAsLocallyModified) {
Element* element = ownerStyleElement();
if (element)
m_resourceContainer->storeStyleElementContent(
DOMNodeIds::idForNode(element), text);
else if (m_origin == protocol::CSS::StyleSheetOriginEnum::Inspector)
m_resourceContainer->storeStyleElementContent(
DOMNodeIds::idForNode(m_pageStyleSheet->ownerDocument()), text);
else
m_resourceContainer->storeStyleSheetContent(finalURL(), text);
}
}
std::unique_ptr<protocol::CSS::CSSStyleSheetHeader>
InspectorStyleSheet::buildObjectForStyleSheetInfo() {
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return nullptr;
Document* document = styleSheet->ownerDocument();
LocalFrame* frame = document ? document->frame() : nullptr;
std::unique_ptr<protocol::CSS::CSSStyleSheetHeader> result =
protocol::CSS::CSSStyleSheetHeader::create()
.setStyleSheetId(id())
.setOrigin(m_origin)
.setDisabled(styleSheet->disabled())
.setSourceURL(url())
.setTitle(styleSheet->title())
.setFrameId(frame ? IdentifiersFactory::frameId(frame) : "")
.setIsInline(styleSheet->isInline() && !startsAtZero())
.setStartLine(
styleSheet->startPositionInSource().m_line.zeroBasedInt())
.setStartColumn(
styleSheet->startPositionInSource().m_column.zeroBasedInt())
.build();
if (hasSourceURL())
result->setHasSourceURL(true);
if (styleSheet->ownerNode())
result->setOwnerNode(DOMNodeIds::idForNode(styleSheet->ownerNode()));
String sourceMapURLValue = sourceMapURL();
if (!sourceMapURLValue.isEmpty())
result->setSourceMapURL(sourceMapURLValue);
return result;
}
std::unique_ptr<protocol::Array<protocol::CSS::Value>>
InspectorStyleSheet::selectorsFromSource(CSSRuleSourceData* sourceData,
const String& sheetText) {
ScriptRegexp comment("/\\*[^]*?\\*/", TextCaseSensitive, MultilineEnabled);
std::unique_ptr<protocol::Array<protocol::CSS::Value>> result =
protocol::Array<protocol::CSS::Value>::create();
const SelectorRangeList& ranges = sourceData->selectorRanges;
for (size_t i = 0, size = ranges.size(); i < size; ++i) {
const SourceRange& range = ranges.at(i);
String selector = sheetText.substring(range.start, range.length());
// We don't want to see any comments in the selector components, only the
// meaningful parts.
int matchLength;
int offset = 0;
while ((offset = comment.match(selector, offset, &matchLength)) >= 0)
selector.replace(offset, matchLength, "");
std::unique_ptr<protocol::CSS::Value> simpleSelector =
protocol::CSS::Value::create()
.setText(selector.stripWhiteSpace())
.build();
simpleSelector->setRange(buildSourceRangeObject(range));
result->addItem(std::move(simpleSelector));
}
return result;
}
std::unique_ptr<protocol::CSS::SelectorList>
InspectorStyleSheet::buildObjectForSelectorList(CSSStyleRule* rule) {
CSSRuleSourceData* sourceData = sourceDataForRule(rule);
std::unique_ptr<protocol::Array<protocol::CSS::Value>> selectors;
// This intentionally does not rely on the source data to avoid catching the
// trailing comments (before the declaration starting '{').
String selectorText = rule->selectorText();
if (sourceData) {
selectors = selectorsFromSource(sourceData, m_text);
} else {
selectors = protocol::Array<protocol::CSS::Value>::create();
const CSSSelectorList& selectorList = rule->styleRule()->selectorList();
for (const CSSSelector* selector = selectorList.first(); selector;
selector = CSSSelectorList::next(*selector))
selectors->addItem(protocol::CSS::Value::create()
.setText(selector->selectorText())
.build());
}
return protocol::CSS::SelectorList::create()
.setSelectors(std::move(selectors))
.setText(selectorText)
.build();
}
static bool canBind(const String& origin) {
return origin != protocol::CSS::StyleSheetOriginEnum::UserAgent &&
origin != protocol::CSS::StyleSheetOriginEnum::Injected;
}
std::unique_ptr<protocol::CSS::CSSRule>
InspectorStyleSheet::buildObjectForRuleWithoutMedia(CSSStyleRule* rule) {
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return nullptr;
std::unique_ptr<protocol::CSS::CSSRule> result =
protocol::CSS::CSSRule::create()
.setSelectorList(buildObjectForSelectorList(rule))
.setOrigin(m_origin)
.setStyle(buildObjectForStyle(rule->style()))
.build();
if (canBind(m_origin)) {
if (!id().isEmpty())
result->setStyleSheetId(id());
}
return result;
}
std::unique_ptr<protocol::CSS::CSSKeyframeRule>
InspectorStyleSheet::buildObjectForKeyframeRule(CSSKeyframeRule* keyframeRule) {
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return nullptr;
std::unique_ptr<protocol::CSS::Value> keyText =
protocol::CSS::Value::create().setText(keyframeRule->keyText()).build();
CSSRuleSourceData* sourceData = sourceDataForRule(keyframeRule);
if (sourceData)
keyText->setRange(buildSourceRangeObject(sourceData->ruleHeaderRange));
std::unique_ptr<protocol::CSS::CSSKeyframeRule> result =
protocol::CSS::CSSKeyframeRule::create()
// TODO(samli): keyText() normalises 'from' and 'to' keyword values.
.setKeyText(std::move(keyText))
.setOrigin(m_origin)
.setStyle(buildObjectForStyle(keyframeRule->style()))
.build();
if (canBind(m_origin) && !id().isEmpty())
result->setStyleSheetId(id());
return result;
}
bool InspectorStyleSheet::getText(String* result) {
if (m_sourceData) {
*result = m_text;
return true;
}
return false;
}
std::unique_ptr<protocol::CSS::SourceRange>
InspectorStyleSheet::ruleHeaderSourceRange(CSSRule* rule) {
if (!m_sourceData)
return nullptr;
CSSRuleSourceData* sourceData = sourceDataForRule(rule);
if (!sourceData)
return nullptr;
return buildSourceRangeObject(sourceData->ruleHeaderRange);
}
std::unique_ptr<protocol::CSS::SourceRange>
InspectorStyleSheet::mediaQueryExpValueSourceRange(CSSRule* rule,
size_t mediaQueryIndex,
size_t mediaQueryExpIndex) {
if (!m_sourceData)
return nullptr;
CSSRuleSourceData* sourceData = sourceDataForRule(rule);
if (!sourceData || !sourceData->mediaSourceData ||
mediaQueryIndex >= sourceData->mediaSourceData->queryData.size())
return nullptr;
CSSMediaQuerySourceData* mediaQueryData =
sourceData->mediaSourceData->queryData.at(mediaQueryIndex).get();
if (mediaQueryExpIndex >= mediaQueryData->expData.size())
return nullptr;
return buildSourceRangeObject(
mediaQueryData->expData.at(mediaQueryExpIndex).valueRange);
}
InspectorStyle* InspectorStyleSheet::inspectorStyle(
CSSStyleDeclaration* style) {
return style ? InspectorStyle::create(
style, sourceDataForRule(style->parentRule()), this)
: nullptr;
}
String InspectorStyleSheet::sourceURL() {
if (!m_sourceURL.isNull())
return m_sourceURL;
if (m_origin != protocol::CSS::StyleSheetOriginEnum::Regular) {
m_sourceURL = "";
return m_sourceURL;
}
String styleSheetText;
bool success = getText(&styleSheetText);
if (success) {
String commentValue = findMagicComment(styleSheetText, "sourceURL");
if (!commentValue.isEmpty()) {
m_sourceURL = commentValue;
return commentValue;
}
}
m_sourceURL = "";
return m_sourceURL;
}
String InspectorStyleSheet::url() {
// "sourceURL" is present only for regular rules, otherwise "origin" should be
// used in the frontend.
if (m_origin != protocol::CSS::StyleSheetOriginEnum::Regular)
return String();
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return String();
if (hasSourceURL())
return sourceURL();
if (styleSheet->isInline() && startsAtZero())
return String();
return finalURL();
}
bool InspectorStyleSheet::hasSourceURL() {
return !sourceURL().isEmpty();
}
bool InspectorStyleSheet::startsAtZero() {
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return true;
return styleSheet->startPositionInSource() == TextPosition::minimumPosition();
}
String InspectorStyleSheet::sourceMapURL() {
if (m_origin != protocol::CSS::StyleSheetOriginEnum::Regular)
return String();
String styleSheetText;
bool success = getText(&styleSheetText);
if (success) {
String commentValue = findMagicComment(styleSheetText, "sourceMappingURL");
if (!commentValue.isEmpty())
return commentValue;
}
return m_pageStyleSheet->contents()->sourceMapURL();
}
CSSRuleSourceData* InspectorStyleSheet::findRuleByHeaderRange(
const SourceRange& sourceRange) {
if (!m_sourceData)
return nullptr;
for (size_t i = 0; i < m_sourceData->size(); ++i) {
CSSRuleSourceData* ruleSourceData = m_sourceData->at(i).get();
if (ruleSourceData->ruleHeaderRange.start == sourceRange.start &&
ruleSourceData->ruleHeaderRange.end == sourceRange.end) {
return ruleSourceData;
}
}
return nullptr;
}
CSSRuleSourceData* InspectorStyleSheet::findRuleByBodyRange(
const SourceRange& sourceRange) {
if (!m_sourceData)
return nullptr;
for (size_t i = 0; i < m_sourceData->size(); ++i) {
CSSRuleSourceData* ruleSourceData = m_sourceData->at(i).get();
if (ruleSourceData->ruleBodyRange.start == sourceRange.start &&
ruleSourceData->ruleBodyRange.end == sourceRange.end) {
return ruleSourceData;
}
}
return nullptr;
}
CSSRule* InspectorStyleSheet::ruleForSourceData(CSSRuleSourceData* sourceData) {
if (!m_sourceData || !sourceData)
return nullptr;
remapSourceDataToCSSOMIfNecessary();
size_t index = m_sourceData->find(sourceData);
if (index == kNotFound)
return nullptr;
IndexMap::iterator it = m_sourceDataToRule.find(index);
if (it == m_sourceDataToRule.end())
return nullptr;
ASSERT(it->value < m_cssomFlatRules.size());
// Check that CSSOM did not mutate this rule.
CSSRule* result = m_cssomFlatRules.at(it->value);
if (canonicalCSSText(m_parsedFlatRules.at(index)) != canonicalCSSText(result))
return nullptr;
return result;
}
CSSRuleSourceData* InspectorStyleSheet::sourceDataForRule(CSSRule* rule) {
if (!m_sourceData || !rule)
return nullptr;
remapSourceDataToCSSOMIfNecessary();
size_t index = m_cssomFlatRules.find(rule);
if (index == kNotFound)
return nullptr;
IndexMap::iterator it = m_ruleToSourceData.find(index);
if (it == m_ruleToSourceData.end())
return nullptr;
ASSERT(it->value < m_sourceData->size());
// Check that CSSOM did not mutate this rule.
CSSRule* parsedRule = m_parsedFlatRules.at(it->value);
if (canonicalCSSText(rule) != canonicalCSSText(parsedRule))
return nullptr;
return m_sourceData->at(it->value).get();
}
void InspectorStyleSheet::remapSourceDataToCSSOMIfNecessary() {
CSSRuleVector cssomRules;
collectFlatRules(m_pageStyleSheet.get(), &cssomRules);
if (cssomRules.size() != m_cssomFlatRules.size()) {
mapSourceDataToCSSOM();
return;
}
for (size_t i = 0; i < m_cssomFlatRules.size(); ++i) {
if (m_cssomFlatRules.at(i) != cssomRules.at(i)) {
mapSourceDataToCSSOM();
return;
}
}
}
void InspectorStyleSheet::mapSourceDataToCSSOM() {
m_ruleToSourceData.clear();
m_sourceDataToRule.clear();
m_cssomFlatRules.clear();
CSSRuleVector& cssomRules = m_cssomFlatRules;
collectFlatRules(m_pageStyleSheet.get(), &cssomRules);
if (!m_sourceData)
return;
CSSRuleVector& parsedRules = m_parsedFlatRules;
Vector<String> cssomRulesText = Vector<String>();
Vector<String> parsedRulesText = Vector<String>();
for (size_t i = 0; i < cssomRules.size(); ++i)
cssomRulesText.append(canonicalCSSText(cssomRules.at(i)));
for (size_t j = 0; j < parsedRules.size(); ++j)
parsedRulesText.append(canonicalCSSText(parsedRules.at(j)));
diff(cssomRulesText, parsedRulesText, &m_ruleToSourceData,
&m_sourceDataToRule);
}
const CSSRuleVector& InspectorStyleSheet::flatRules() {
remapSourceDataToCSSOMIfNecessary();
return m_cssomFlatRules;
}
bool InspectorStyleSheet::resourceStyleSheetText(String* result) {
if (m_origin == protocol::CSS::StyleSheetOriginEnum::Injected ||
m_origin == protocol::CSS::StyleSheetOriginEnum::UserAgent)
return false;
if (!m_pageStyleSheet->ownerDocument())
return false;
KURL url(ParsedURLString, m_pageStyleSheet->href());
if (m_resourceContainer->loadStyleSheetContent(url, result))
return true;
bool base64Encoded;
bool success = m_networkAgent->fetchResourceContent(
m_pageStyleSheet->ownerDocument(), url, result, &base64Encoded);
return success && !base64Encoded;
}
Element* InspectorStyleSheet::ownerStyleElement() {
Node* ownerNode = m_pageStyleSheet->ownerNode();
if (!ownerNode || !ownerNode->isElementNode())
return nullptr;
Element* ownerElement = toElement(ownerNode);
if (!isHTMLStyleElement(ownerElement) && !isSVGStyleElement(ownerElement))
return nullptr;
return ownerElement;
}
bool InspectorStyleSheet::inlineStyleSheetText(String* result) {
Element* ownerElement = ownerStyleElement();
if (!ownerElement)
return false;
if (m_resourceContainer->loadStyleElementContent(
DOMNodeIds::idForNode(ownerElement), result))
return true;
*result = ownerElement->textContent();
return true;
}
bool InspectorStyleSheet::inspectorStyleSheetText(String* result) {
if (m_origin != protocol::CSS::StyleSheetOriginEnum::Inspector)
return false;
if (!m_pageStyleSheet->ownerDocument())
return false;
if (m_resourceContainer->loadStyleElementContent(
DOMNodeIds::idForNode(m_pageStyleSheet->ownerDocument()), result))
return true;
*result = "";
return true;
}
InspectorStyleSheetForInlineStyle* InspectorStyleSheetForInlineStyle::create(
Element* element,
Listener* listener) {
return new InspectorStyleSheetForInlineStyle(element, listener);
}
InspectorStyleSheetForInlineStyle::InspectorStyleSheetForInlineStyle(
Element* element,
Listener* listener)
: InspectorStyleSheetBase(listener), m_element(element) {
ASSERT(m_element);
}
void InspectorStyleSheetForInlineStyle::didModifyElementAttribute() {
m_inspectorStyle.clear();
}
bool InspectorStyleSheetForInlineStyle::setText(
const String& text,
ExceptionState& exceptionState) {
if (!verifyStyleText(&m_element->document(), text)) {
exceptionState.throwDOMException(SyntaxError, "Style text is not valid.");
return false;
}
{
InspectorCSSAgent::InlineStyleOverrideScope overrideScope(
m_element->ownerDocument());
m_element->setAttribute("style", AtomicString(text), exceptionState);
}
if (!exceptionState.hadException())
onStyleSheetTextChanged();
return !exceptionState.hadException();
}
bool InspectorStyleSheetForInlineStyle::getText(String* result) {
*result = elementStyleText();
return true;
}
InspectorStyle* InspectorStyleSheetForInlineStyle::inspectorStyle(
CSSStyleDeclaration* style) {
if (!m_inspectorStyle)
m_inspectorStyle =
InspectorStyle::create(m_element->style(), ruleSourceData(), this);
return m_inspectorStyle;
}
PassRefPtr<CSSRuleSourceData>
InspectorStyleSheetForInlineStyle::ruleSourceData() {
const String& text = elementStyleText();
RefPtr<CSSRuleSourceData> ruleSourceData;
if (text.isEmpty()) {
ruleSourceData = CSSRuleSourceData::create(StyleRule::Style);
ruleSourceData->ruleBodyRange.start = 0;
ruleSourceData->ruleBodyRange.end = 0;
} else {
RuleSourceDataList ruleSourceDataResult;
StyleSheetHandler handler(text, &m_element->document(),
&ruleSourceDataResult);
CSSParser::parseDeclarationListForInspector(
parserContextForDocument(&m_element->document()), text, handler);
ruleSourceData = ruleSourceDataResult.first().release();
}
return ruleSourceData.release();
}
CSSStyleDeclaration* InspectorStyleSheetForInlineStyle::inlineStyle() {
return m_element->style();
}
const String& InspectorStyleSheetForInlineStyle::elementStyleText() {
return m_element->getAttribute("style").getString();
}
DEFINE_TRACE(InspectorStyleSheetForInlineStyle) {
visitor->trace(m_element);
visitor->trace(m_inspectorStyle);
InspectorStyleSheetBase::trace(visitor);
}
} // namespace blink