blob: 2edb8a14221ae3ab67fb16eafc877db0b2557d82 [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 "config.h"
#include "InspectorStyleSheet.h"
#if ENABLE(INSPECTOR)
#include "CSSImportRule.h"
#include "CSSMediaRule.h"
#include "CSSParser.h"
#include "CSSPropertySourceData.h"
#include "CSSRule.h"
#include "CSSRuleList.h"
#include "CSSStyleRule.h"
#include "CSSStyleSelector.h"
#include "CSSStyleSheet.h"
#include "Document.h"
#include "Element.h"
#include "HTMLHeadElement.h"
#include "HTMLParserIdioms.h"
#include "InspectorCSSAgent.h"
#include "InspectorResourceAgent.h"
#include "InspectorValues.h"
#include "Node.h"
#include "StyleSheetList.h"
#include "WebKitCSSKeyframesRule.h"
#include <wtf/OwnPtr.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/Vector.h>
class ParsedStyleSheet {
public:
typedef Vector<RefPtr<WebCore::CSSRuleSourceData> > SourceData;
ParsedStyleSheet();
WebCore::CSSStyleSheet* cssStyleSheet() const { return m_parserOutput; }
const String& text() const { return m_text; }
void setText(const String& text);
bool hasText() const { return m_hasText; }
SourceData* sourceData() const { return m_sourceData.get(); }
void setSourceData(PassOwnPtr<SourceData> sourceData);
bool hasSourceData() const { return m_sourceData; }
RefPtr<WebCore::CSSRuleSourceData> ruleSourceDataAt(unsigned index) const;
private:
// StyleSheet constructed while parsing m_text.
WebCore::CSSStyleSheet* m_parserOutput;
String m_text;
bool m_hasText;
OwnPtr<SourceData> m_sourceData;
};
ParsedStyleSheet::ParsedStyleSheet()
: m_parserOutput(0)
, m_hasText(false)
{
}
void ParsedStyleSheet::setText(const String& text)
{
m_hasText = true;
m_text = text;
setSourceData(0);
}
void ParsedStyleSheet::setSourceData(PassOwnPtr<SourceData> sourceData)
{
m_sourceData = sourceData;
}
RefPtr<WebCore::CSSRuleSourceData> ParsedStyleSheet::ruleSourceDataAt(unsigned index) const
{
if (!hasSourceData() || index >= m_sourceData->size())
return 0;
return m_sourceData->at(index);
}
namespace WebCore {
static PassRefPtr<InspectorObject> buildSourceRangeObject(const SourceRange& range)
{
RefPtr<InspectorObject> result = InspectorObject::create();
result->setNumber("start", range.start);
result->setNumber("end", range.end);
return result.release();
}
static PassRefPtr<CSSRuleList> asCSSRuleList(StyleBase* styleBase)
{
if (!styleBase)
return 0;
if (styleBase->isCSSStyleSheet())
return CSSRuleList::create(static_cast<CSSStyleSheet*>(styleBase), true);
if (styleBase->isRule()) {
unsigned ruleType = static_cast<CSSRule*>(styleBase)->type();
RefPtr<CSSRuleList> result = 0;
switch (ruleType) {
case CSSRule::MEDIA_RULE:
result = static_cast<CSSMediaRule*>(styleBase)->cssRules();
break;
case CSSRule::WEBKIT_KEYFRAMES_RULE:
result = static_cast<WebKitCSSKeyframesRule*>(styleBase)->cssRules();
break;
case CSSRule::IMPORT_RULE:
case CSSRule::PAGE_RULE:
default:
return 0;
}
return result.release();
}
return 0;
}
PassRefPtr<InspectorStyle> InspectorStyle::create(const InspectorCSSId& styleId, PassRefPtr<CSSStyleDeclaration> style, InspectorStyleSheet* parentStyleSheet)
{
return adoptRef(new InspectorStyle(styleId, style, parentStyleSheet));
}
InspectorStyle::InspectorStyle(const InspectorCSSId& styleId, PassRefPtr<CSSStyleDeclaration> style, InspectorStyleSheet* parentStyleSheet)
: m_styleId(styleId)
, m_style(style)
, m_parentStyleSheet(parentStyleSheet)
{
ASSERT(m_style);
}
InspectorStyle::~InspectorStyle()
{
}
PassRefPtr<InspectorObject> InspectorStyle::buildObjectForStyle() const
{
RefPtr<InspectorObject> result = InspectorObject::create();
if (!m_styleId.isEmpty())
result->setValue("styleId", m_styleId.asInspectorValue());
result->setString("width", m_style->getPropertyValue("width"));
result->setString("height", m_style->getPropertyValue("height"));
RefPtr<CSSRuleSourceData> sourceData = m_parentStyleSheet ? m_parentStyleSheet->ruleSourceDataFor(m_style.get()) : 0;
if (sourceData)
result->setObject("range", buildSourceRangeObject(sourceData->styleSourceData->styleBodyRange));
populateObjectWithStyleProperties(result.get());
return result.release();
}
// This method does the following preprocessing of |propertyText| with |overwrite| == false and |index| past the last active property:
// - If the last property (if present) has no subsequent whitespace in the style declaration, a space is prepended to |propertyText|.
// - If the last property (if present) has no closing ";", the ";" is prepended to the current |propertyText| value.
//
// The propertyText (if not empty) is checked to be a valid style declaration (containing at least one property). If not,
// the method returns false (denoting an error).
bool InspectorStyle::setPropertyText(ErrorString* errorString, unsigned index, const String& propertyText, bool overwrite)
{
ASSERT(m_parentStyleSheet);
if (!m_parentStyleSheet->ensureParsedDataReady()) {
*errorString = "Internal error: no stylesheet parsed data available";
return false;
}
Vector<InspectorStyleProperty> allProperties;
populateAllProperties(&allProperties);
unsigned propertyStart = 0; // Need to initialize to make the compiler happy.
long propertyLengthDelta;
if (propertyText.stripWhiteSpace().length()) {
RefPtr<CSSMutableStyleDeclaration> tempMutableStyle = CSSMutableStyleDeclaration::create();
CSSParser p;
RefPtr<CSSStyleSourceData> sourceData = CSSStyleSourceData::create();
p.parseDeclaration(tempMutableStyle.get(), propertyText + " -webkit-boguz-propertee: none", &sourceData);
Vector<CSSPropertySourceData>& propertyData = sourceData->propertyData;
unsigned propertyCount = propertyData.size();
// At least one property + the bogus property added just above should be present.
if (propertyCount < 2) {
*errorString = "Invalid property value";
return false;
}
// Check for a proper propertyText termination (the parser could at least restore to the PROPERTY_NAME state).
if (propertyData.at(propertyCount - 1).name != "-webkit-boguz-propertee") {
*errorString = "Invalid property value";
return false;
}
}
if (overwrite) {
ASSERT(index < allProperties.size());
InspectorStyleProperty& property = allProperties.at(index);
propertyStart = property.sourceData.range.start;
unsigned propertyEnd = property.sourceData.range.end;
unsigned oldLength = propertyEnd - propertyStart;
unsigned newLength = propertyText.length();
propertyLengthDelta = newLength - oldLength;
if (!property.disabled) {
bool success = replacePropertyInStyleText(property, propertyText);
if (!success) {
*errorString = "Internal error: could not replace property value";
return false;
}
} else {
unsigned textLength = propertyText.length();
unsigned disabledIndex = disabledIndexByOrdinal(index, false, allProperties);
if (!textLength) {
// Delete disabled property.
m_disabledProperties.remove(disabledIndex);
} else {
// Patch disabled property text.
m_disabledProperties.at(disabledIndex).rawText = propertyText;
}
// We should not shift subsequent disabled properties when altering a disabled property.
return true;
}
} else {
// Insert at index.
RefPtr<CSSRuleSourceData> sourceData = m_parentStyleSheet->ruleSourceDataFor(m_style.get());
if (!sourceData) {
*errorString = "Internal error: no CSS rule source found";
return false;
}
String text;
bool success = styleText(&text);
if (!success) {
*errorString = "Internal error: could not fetch style text";
return false;
}
propertyLengthDelta = propertyText.length();
bool insertLast = true;
if (index < allProperties.size()) {
InspectorStyleProperty& property = allProperties.at(index);
if (property.hasSource) {
propertyStart = property.sourceData.range.start;
// If inserting before a disabled property, it should be shifted, too.
insertLast = false;
}
}
String textToSet = propertyText;
if (insertLast) {
propertyStart = sourceData->styleSourceData->styleBodyRange.end - sourceData->styleSourceData->styleBodyRange.start;
if (propertyStart && propertyText.length()) {
const UChar* characters = text.characters();
unsigned curPos = propertyStart - 1; // The last position of style declaration, since propertyStart points past one.
while (curPos && isHTMLSpace(characters[curPos]))
--curPos;
if (curPos && characters[curPos] != ';') {
// Prepend a ";" to the property text if appending to a style declaration where
// the last property has no trailing ";".
textToSet.insert("; ", 0);
} else if (!isHTMLSpace(characters[propertyStart - 1])) {
// Prepend a " " if the last declaration character is not an HTML space.
textToSet.insert(" ", 0);
}
}
}
text.insert(textToSet, propertyStart);
m_parentStyleSheet->setStyleText(m_style.get(), text);
}
// Recompute subsequent disabled property ranges if acting on a non-disabled property.
shiftDisabledProperties(disabledIndexByOrdinal(index, true, allProperties), propertyLengthDelta);
return true;
}
bool InspectorStyle::toggleProperty(ErrorString* errorString, unsigned index, bool disable)
{
ASSERT(m_parentStyleSheet);
if (!m_parentStyleSheet->ensureParsedDataReady()) {
*errorString = "Can toggle only source-based properties";
return false;
}
RefPtr<CSSRuleSourceData> sourceData = m_parentStyleSheet->ruleSourceDataFor(m_style.get());
if (!sourceData) {
*errorString = "Internal error: No source data for the style found";
return false;
}
Vector<InspectorStyleProperty> allProperties;
populateAllProperties(&allProperties);
if (index >= allProperties.size()) {
*errorString = "Property index is outside of property range";
return false;
}
InspectorStyleProperty& property = allProperties.at(index);
if (property.disabled == disable)
return true; // Idempotent operation.
bool success;
if (!disable)
success = enableProperty(index, allProperties);
else
success = disableProperty(index, allProperties);
return success;
}
// static
unsigned InspectorStyle::disabledIndexByOrdinal(unsigned ordinal, bool canUseSubsequent, Vector<InspectorStyleProperty>& allProperties)
{
unsigned disabledIndex = 0;
for (unsigned i = 0, size = allProperties.size(); i < size; ++i) {
InspectorStyleProperty& property = allProperties.at(i);
if (property.disabled) {
if (i == ordinal || (canUseSubsequent && i > ordinal))
return disabledIndex;
++disabledIndex;
}
}
return UINT_MAX;
}
bool InspectorStyle::styleText(String* result) const
{
// Precondition: m_parentStyleSheet->ensureParsedDataReady() has been called successfully.
RefPtr<CSSRuleSourceData> sourceData = m_parentStyleSheet->ruleSourceDataFor(m_style.get());
if (!sourceData)
return false;
String styleSheetText;
bool success = m_parentStyleSheet->text(&styleSheetText);
if (!success)
return false;
SourceRange& bodyRange = sourceData->styleSourceData->styleBodyRange;
*result = styleSheetText.substring(bodyRange.start, bodyRange.end - bodyRange.start);
return true;
}
bool InspectorStyle::disableProperty(unsigned indexToDisable, Vector<InspectorStyleProperty>& allProperties)
{
// Precondition: |indexToEnable| points to an enabled property.
const InspectorStyleProperty& property = allProperties.at(indexToDisable);
unsigned propertyStart = property.sourceData.range.start;
InspectorStyleProperty disabledProperty(property);
String oldStyleText;
bool success = styleText(&oldStyleText);
if (!success)
return false;
disabledProperty.setRawTextFromStyleDeclaration(oldStyleText);
disabledProperty.disabled = true;
disabledProperty.sourceData.range.end = propertyStart;
// This may have to be negated below.
long propertyLength = property.sourceData.range.end - propertyStart;
success = replacePropertyInStyleText(property, "");
if (!success)
return false;
// Add disabled property at correct position.
unsigned insertionIndex = disabledIndexByOrdinal(indexToDisable, true, allProperties);
if (insertionIndex == UINT_MAX)
m_disabledProperties.append(disabledProperty);
else {
m_disabledProperties.insert(insertionIndex, disabledProperty);
shiftDisabledProperties(insertionIndex + 1, -propertyLength); // Property removed from text - shift these back.
}
return true;
}
bool InspectorStyle::enableProperty(unsigned indexToEnable, Vector<InspectorStyleProperty>& allProperties)
{
// Precondition: |indexToEnable| points to a disabled property.
unsigned disabledIndex = disabledIndexByOrdinal(indexToEnable, false, allProperties);
if (disabledIndex == UINT_MAX)
return false;
InspectorStyleProperty disabledProperty = m_disabledProperties.at(disabledIndex);
m_disabledProperties.remove(disabledIndex);
bool success = replacePropertyInStyleText(disabledProperty, disabledProperty.rawText);
if (success)
shiftDisabledProperties(disabledIndex, disabledProperty.rawText.length());
return success;
}
bool InspectorStyle::populateAllProperties(Vector<InspectorStyleProperty>* result) const
{
HashSet<String> foundShorthands;
HashSet<String> sourcePropertyNames;
unsigned disabledIndex = 0;
unsigned disabledLength = m_disabledProperties.size();
InspectorStyleProperty disabledProperty;
if (disabledIndex < disabledLength)
disabledProperty = m_disabledProperties.at(disabledIndex);
RefPtr<CSSRuleSourceData> sourceData = (m_parentStyleSheet && m_parentStyleSheet->ensureParsedDataReady()) ? m_parentStyleSheet->ruleSourceDataFor(m_style.get()) : 0;
Vector<CSSPropertySourceData>* sourcePropertyData = sourceData ? &(sourceData->styleSourceData->propertyData) : 0;
if (sourcePropertyData) {
String styleDeclaration;
bool isStyleTextKnown = styleText(&styleDeclaration);
ASSERT_UNUSED(isStyleTextKnown, isStyleTextKnown);
for (Vector<CSSPropertySourceData>::const_iterator it = sourcePropertyData->begin(); it != sourcePropertyData->end(); ++it) {
while (disabledIndex < disabledLength && disabledProperty.sourceData.range.start <= it->range.start) {
result->append(disabledProperty);
if (++disabledIndex < disabledLength)
disabledProperty = m_disabledProperties.at(disabledIndex);
}
InspectorStyleProperty p(*it, true, false);
p.setRawTextFromStyleDeclaration(styleDeclaration);
result->append(p);
sourcePropertyNames.add(it->name.lower());
}
}
while (disabledIndex < disabledLength) {
disabledProperty = m_disabledProperties.at(disabledIndex++);
result->append(disabledProperty);
}
for (int i = 0, size = m_style->length(); i < size; ++i) {
String name = m_style->item(i);
if (sourcePropertyNames.contains(name.lower()))
continue;
sourcePropertyNames.add(name.lower());
result->append(InspectorStyleProperty(CSSPropertySourceData(name, m_style->getPropertyValue(name), !m_style->getPropertyPriority(name).isEmpty(), true, SourceRange()), false, false));
}
return true;
}
void InspectorStyle::populateObjectWithStyleProperties(InspectorObject* result) const
{
Vector<InspectorStyleProperty> properties;
populateAllProperties(&properties);
RefPtr<InspectorArray> propertiesObject = InspectorArray::create();
RefPtr<InspectorArray> shorthandEntries = InspectorArray::create();
HashMap<String, RefPtr<InspectorObject> > propertyNameToPreviousActiveProperty;
HashSet<String> foundShorthands;
for (Vector<InspectorStyleProperty>::iterator it = properties.begin(), itEnd = properties.end(); it != itEnd; ++it) {
const CSSPropertySourceData& propertyEntry = it->sourceData;
const String& name = propertyEntry.name;
RefPtr<InspectorObject> property = InspectorObject::create();
propertiesObject->pushObject(property);
String status = it->disabled ? "disabled" : "active";
// Default "parsedOk" == true.
if (!propertyEntry.parsedOk)
property->setBoolean("parsedOk", false);
if (it->hasRawText())
property->setString("text", it->rawText);
property->setString("name", name);
property->setString("value", propertyEntry.value);
// Default "priority" == "".
if (propertyEntry.important)
property->setString("priority", "important");
if (!it->disabled) {
if (it->hasSource) {
property->setBoolean("implicit", false);
property->setObject("range", buildSourceRangeObject(propertyEntry.range));
// Parsed property overrides any property with the same name. Non-parsed property overrides
// previous non-parsed property with the same name (if any).
bool shouldInactivate = false;
HashMap<String, RefPtr<InspectorObject> >::iterator activeIt = propertyNameToPreviousActiveProperty.find(name);
if (activeIt != propertyNameToPreviousActiveProperty.end()) {
if (propertyEntry.parsedOk)
shouldInactivate = true;
else {
bool previousParsedOk;
bool success = activeIt->second->getBoolean("parsedOk", &previousParsedOk);
if (success && !previousParsedOk)
shouldInactivate = true;
}
} else
propertyNameToPreviousActiveProperty.set(name, property);
if (shouldInactivate) {
activeIt->second->setString("status", "inactive");
activeIt->second->remove("shorthandName");
propertyNameToPreviousActiveProperty.set(name, property);
}
} else {
bool implicit = m_style->isPropertyImplicit(name);
// Default "implicit" == false.
if (implicit)
property->setBoolean("implicit", true);
status = "";
}
}
// Default "status" == "style".
if (!status.isEmpty())
property->setString("status", status);
if (propertyEntry.parsedOk) {
// Both for style-originated and parsed source properties.
String shorthand = m_style->getPropertyShorthand(name);
if (!shorthand.isEmpty()) {
// Default "shorthandName" == "".
property->setString("shorthandName", shorthand);
if (!foundShorthands.contains(shorthand)) {
foundShorthands.add(shorthand);
RefPtr<InspectorObject> shorthandEntry = InspectorObject::create();
shorthandEntry->setString("name", shorthand);
shorthandEntry->setString("value", shorthandValue(shorthand));
shorthandEntries->pushObject(shorthandEntry.release());
}
}
}
// else shorthandName is not set
}
result->setArray("cssProperties", propertiesObject);
result->setArray("shorthandEntries", shorthandEntries);
}
void InspectorStyle::shiftDisabledProperties(unsigned fromIndex, long delta)
{
for (unsigned i = fromIndex, size = m_disabledProperties.size(); i < size; ++i) {
SourceRange& range = m_disabledProperties.at(i).sourceData.range;
range.start += delta;
range.end += delta;
}
}
bool InspectorStyle::replacePropertyInStyleText(const InspectorStyleProperty& property, const String& newText)
{
// Precondition: m_parentStyleSheet->ensureParsedDataReady() has been called successfully.
String text;
bool success = styleText(&text);
if (!success)
return false;
const SourceRange& range = property.sourceData.range;
text.replace(range.start, range.end - range.start, newText);
success = m_parentStyleSheet->setStyleText(m_style.get(), text);
return success;
}
String InspectorStyle::shorthandValue(const String& shorthandProperty) const
{
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 (value.length())
value.append(" ");
value.append(individualValue);
}
}
return value;
}
String InspectorStyle::shorthandPriority(const String& shorthandProperty) const
{
String priority = m_style->getPropertyPriority(shorthandProperty);
if (priority.isEmpty()) {
for (unsigned i = 0; i < m_style->length(); ++i) {
String individualProperty = m_style->item(i);
if (m_style->getPropertyShorthand(individualProperty) != shorthandProperty)
continue;
priority = m_style->getPropertyPriority(individualProperty);
break;
}
}
return priority;
}
Vector<String> InspectorStyle::longhandProperties(const String& shorthandProperty) const
{
Vector<String> properties;
HashSet<String> foundProperties;
for (unsigned i = 0; i < m_style->length(); ++i) {
String individualProperty = m_style->item(i);
if (foundProperties.contains(individualProperty) || m_style->getPropertyShorthand(individualProperty) != shorthandProperty)
continue;
foundProperties.add(individualProperty);
properties.append(individualProperty);
}
return properties;
}
PassRefPtr<InspectorStyleSheet> InspectorStyleSheet::create(const String& id, PassRefPtr<CSSStyleSheet> pageStyleSheet, const String& origin, const String& documentURL)
{
return adoptRef(new InspectorStyleSheet(id, pageStyleSheet, origin, documentURL));
}
InspectorStyleSheet::InspectorStyleSheet(const String& id, PassRefPtr<CSSStyleSheet> pageStyleSheet, const String& origin, const String& documentURL)
: m_id(id)
, m_pageStyleSheet(pageStyleSheet)
, m_origin(origin)
, m_documentURL(documentURL)
, m_isRevalidating(false)
{
m_parsedStyleSheet = new ParsedStyleSheet();
}
InspectorStyleSheet::~InspectorStyleSheet()
{
delete m_parsedStyleSheet;
}
String InspectorStyleSheet::finalURL() const
{
if (m_pageStyleSheet && !m_pageStyleSheet->finalURL().isEmpty())
return m_pageStyleSheet->finalURL().string();
return m_documentURL;
}
void InspectorStyleSheet::reparseStyleSheet(const String& text)
{
for (unsigned i = 0, size = m_pageStyleSheet->length(); i < size; ++i)
m_pageStyleSheet->remove(0);
m_pageStyleSheet->parseString(text, m_pageStyleSheet->useStrictParsing());
m_pageStyleSheet->styleSheetChanged();
m_inspectorStyles.clear();
}
bool InspectorStyleSheet::setText(const String& text)
{
if (!m_parsedStyleSheet)
return false;
m_parsedStyleSheet->setText(text);
m_flatRules.clear();
return true;
}
bool InspectorStyleSheet::setRuleSelector(const InspectorCSSId& id, const String& selector)
{
CSSStyleRule* rule = ruleForId(id);
if (!rule)
return false;
CSSStyleSheet* styleSheet = InspectorCSSAgent::parentStyleSheet(rule);
if (!styleSheet || !ensureParsedDataReady())
return false;
rule->setSelectorText(selector);
RefPtr<CSSRuleSourceData> sourceData = ruleSourceDataFor(rule->style());
if (!sourceData)
return false;
String sheetText = m_parsedStyleSheet->text();
sheetText.replace(sourceData->selectorListRange.start, sourceData->selectorListRange.end - sourceData->selectorListRange.start, selector);
m_parsedStyleSheet->setText(sheetText);
return true;
}
CSSStyleRule* InspectorStyleSheet::addRule(const String& selector)
{
String styleSheetText;
bool success = text(&styleSheetText);
if (!success)
return 0;
ExceptionCode ec = 0;
m_pageStyleSheet->addRule(selector, "", ec);
if (ec)
return 0;
RefPtr<CSSRuleList> rules = m_pageStyleSheet->cssRules();
ASSERT(rules->length());
CSSStyleRule* rule = InspectorCSSAgent::asCSSStyleRule(rules->item(rules->length() - 1));
ASSERT(rule);
if (styleSheetText.length())
styleSheetText += "\n";
styleSheetText += selector;
styleSheetText += " {}";
// Using setText() as this operation changes the style sheet rule set.
setText(styleSheetText);
return rule;
}
CSSStyleRule* InspectorStyleSheet::ruleForId(const InspectorCSSId& id) const
{
if (!m_pageStyleSheet)
return 0;
ASSERT(!id.isEmpty());
ensureFlatRules();
return id.ordinal() >= m_flatRules.size() ? 0 : m_flatRules.at(id.ordinal());
}
PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForStyleSheet()
{
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return 0;
RefPtr<InspectorObject> result = InspectorObject::create();
result->setString("styleSheetId", id());
RefPtr<CSSRuleList> cssRuleList = CSSRuleList::create(styleSheet, true);
RefPtr<InspectorArray> cssRules = buildArrayForRuleList(cssRuleList.get());
result->setArray("rules", cssRules.release());
String styleSheetText;
bool success = text(&styleSheetText);
if (success)
result->setString("text", styleSheetText);
return result.release();
}
PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForStyleSheetInfo()
{
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return 0;
RefPtr<InspectorObject> result = InspectorObject::create();
result->setString("styleSheetId", id());
result->setBoolean("disabled", styleSheet->disabled());
result->setString("sourceURL", finalURL());
result->setString("title", styleSheet->title());
return result.release();
}
PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForRule(CSSStyleRule* rule)
{
CSSStyleSheet* styleSheet = pageStyleSheet();
if (!styleSheet)
return 0;
RefPtr<InspectorObject> result = InspectorObject::create();
result->setString("selectorText", rule->selectorText());
// "sourceURL" is present only for regular rules, otherwise "origin" should be used in the frontend.
if (!m_origin.length())
result->setString("sourceURL", finalURL());
result->setNumber("sourceLine", rule->sourceLine());
result->setString("origin", m_origin);
result->setObject("style", buildObjectForStyle(rule->style()));
if (canBind()) {
InspectorCSSId id(ruleId(rule));
if (!id.isEmpty())
result->setValue("ruleId", id.asInspectorValue());
}
RefPtr<CSSRuleSourceData> sourceData;
if (ensureParsedDataReady())
sourceData = ruleSourceDataFor(rule->style());
if (sourceData) {
RefPtr<InspectorObject> selectorRange = InspectorObject::create();
selectorRange->setNumber("start", sourceData->selectorListRange.start);
selectorRange->setNumber("end", sourceData->selectorListRange.end);
result->setObject("selectorRange", selectorRange.release());
}
return result.release();
}
PassRefPtr<InspectorObject> InspectorStyleSheet::buildObjectForStyle(CSSStyleDeclaration* style)
{
RefPtr<CSSRuleSourceData> sourceData;
if (ensureParsedDataReady())
sourceData = ruleSourceDataFor(style);
InspectorCSSId id = ruleOrStyleId(style);
if (id.isEmpty()) {
RefPtr<InspectorObject> bogusStyle = InspectorObject::create();
bogusStyle->setArray("cssProperties", InspectorArray::create());
bogusStyle->setObject("shorthandValues", InspectorObject::create());
bogusStyle->setObject("properties", InspectorObject::create());
return bogusStyle.release();
}
RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
RefPtr<InspectorObject> result = inspectorStyle->buildObjectForStyle();
// Style text cannot be retrieved without stylesheet, so set cssText here.
if (sourceData) {
String sheetText;
bool success = text(&sheetText);
if (success) {
const SourceRange& bodyRange = sourceData->styleSourceData->styleBodyRange;
result->setString("cssText", sheetText.substring(bodyRange.start, bodyRange.end - bodyRange.start));
}
}
return result.release();
}
bool InspectorStyleSheet::setPropertyText(ErrorString* errorString, const InspectorCSSId& id, unsigned propertyIndex, const String& text, bool overwrite)
{
RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
if (!inspectorStyle) {
*errorString = "No style found for given id";
return false;
}
return inspectorStyle->setPropertyText(errorString, propertyIndex, text, overwrite);
}
bool InspectorStyleSheet::toggleProperty(ErrorString* errorString, const InspectorCSSId& id, unsigned propertyIndex, bool disable)
{
RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
if (!inspectorStyle) {
*errorString = "No style found for given id";
return false;
}
bool success = inspectorStyle->toggleProperty(errorString, propertyIndex, disable);
if (success) {
if (disable)
rememberInspectorStyle(inspectorStyle);
else if (!inspectorStyle->hasDisabledProperties())
forgetInspectorStyle(inspectorStyle->cssStyle());
}
return success;
}
bool InspectorStyleSheet::text(String* result) const
{
if (!ensureText())
return false;
*result = m_parsedStyleSheet->text();
return true;
}
CSSStyleDeclaration* InspectorStyleSheet::styleForId(const InspectorCSSId& id) const
{
CSSStyleRule* rule = ruleForId(id);
if (!rule)
return 0;
return rule->style();
}
PassRefPtr<InspectorStyle> InspectorStyleSheet::inspectorStyleForId(const InspectorCSSId& id)
{
CSSStyleDeclaration* style = styleForId(id);
if (!style)
return 0;
InspectorStyleMap::iterator it = m_inspectorStyles.find(style);
if (it == m_inspectorStyles.end()) {
RefPtr<InspectorStyle> inspectorStyle = InspectorStyle::create(id, style, this);
return inspectorStyle.release();
}
return it->second;
}
void InspectorStyleSheet::rememberInspectorStyle(RefPtr<InspectorStyle> inspectorStyle)
{
m_inspectorStyles.set(inspectorStyle->cssStyle(), inspectorStyle);
}
void InspectorStyleSheet::forgetInspectorStyle(CSSStyleDeclaration* style)
{
m_inspectorStyles.remove(style);
}
InspectorCSSId InspectorStyleSheet::ruleOrStyleId(CSSStyleDeclaration* style) const
{
unsigned index = ruleIndexByStyle(style);
if (index != UINT_MAX)
return InspectorCSSId(id(), index);
return InspectorCSSId();
}
Document* InspectorStyleSheet::ownerDocument() const
{
return m_pageStyleSheet->document();
}
RefPtr<CSSRuleSourceData> InspectorStyleSheet::ruleSourceDataFor(CSSStyleDeclaration* style) const
{
return m_parsedStyleSheet->ruleSourceDataAt(ruleIndexByStyle(style));
}
unsigned InspectorStyleSheet::ruleIndexByStyle(CSSStyleDeclaration* pageStyle) const
{
ensureFlatRules();
unsigned index = 0;
for (unsigned i = 0, size = m_flatRules.size(); i < size; ++i) {
if (m_flatRules.at(i)->style() == pageStyle)
return index;
++index;
}
return UINT_MAX;
}
bool InspectorStyleSheet::ensureParsedDataReady()
{
return ensureText() && ensureSourceData();
}
bool InspectorStyleSheet::ensureText() const
{
if (!m_parsedStyleSheet)
return false;
if (m_parsedStyleSheet->hasText())
return true;
String text;
bool success = originalStyleSheetText(&text);
if (success)
m_parsedStyleSheet->setText(text);
// No need to clear m_flatRules here - it's empty.
return success;
}
bool InspectorStyleSheet::ensureSourceData()
{
if (m_parsedStyleSheet->hasSourceData())
return true;
if (!m_parsedStyleSheet->hasText())
return false;
RefPtr<CSSStyleSheet> newStyleSheet = CSSStyleSheet::create();
CSSParser p;
StyleRuleRangeMap ruleRangeMap;
p.parseSheet(newStyleSheet.get(), m_parsedStyleSheet->text(), 0, &ruleRangeMap);
OwnPtr<ParsedStyleSheet::SourceData> rangesVector(new ParsedStyleSheet::SourceData());
Vector<CSSStyleRule*> rules;
RefPtr<CSSRuleList> ruleList = asCSSRuleList(newStyleSheet.get());
collectFlatRules(ruleList, &rules);
for (unsigned i = 0, size = rules.size(); i < size; ++i) {
StyleRuleRangeMap::iterator it = ruleRangeMap.find(rules.at(i));
if (it != ruleRangeMap.end()) {
fixUnparsedPropertyRanges(it->second.get(), m_parsedStyleSheet->text());
rangesVector->append(it->second);
}
}
m_parsedStyleSheet->setSourceData(rangesVector.release());
return m_parsedStyleSheet->hasSourceData();
}
void InspectorStyleSheet::ensureFlatRules() const
{
// We are fine with redoing this for empty stylesheets as this will run fast.
if (m_flatRules.isEmpty())
collectFlatRules(asCSSRuleList(pageStyleSheet()), &m_flatRules);
}
bool InspectorStyleSheet::setStyleText(CSSStyleDeclaration* style, const String& text)
{
if (!pageStyleSheet())
return false;
if (!ensureParsedDataReady())
return false;
String patchedStyleSheetText;
bool success = styleSheetTextWithChangedStyle(style, text, &patchedStyleSheetText);
if (!success)
return false;
InspectorCSSId id = ruleOrStyleId(style);
if (id.isEmpty())
return false;
ExceptionCode ec = 0;
style->setCssText(text, ec);
if (!ec)
m_parsedStyleSheet->setText(patchedStyleSheetText);
return !ec;
}
bool InspectorStyleSheet::styleSheetTextWithChangedStyle(CSSStyleDeclaration* style, const String& newStyleText, String* result)
{
if (!style)
return false;
if (!ensureParsedDataReady())
return false;
RefPtr<CSSRuleSourceData> sourceData = ruleSourceDataFor(style);
unsigned bodyStart = sourceData->styleSourceData->styleBodyRange.start;
unsigned bodyEnd = sourceData->styleSourceData->styleBodyRange.end;
ASSERT(bodyStart <= bodyEnd);
String text = m_parsedStyleSheet->text();
ASSERT(bodyEnd <= text.length()); // bodyEnd is exclusive
text.replace(bodyStart, bodyEnd - bodyStart, newStyleText);
*result = text;
return true;
}
InspectorCSSId InspectorStyleSheet::ruleId(CSSStyleRule* rule) const
{
return ruleOrStyleId(rule->style());
}
void InspectorStyleSheet::revalidateStyle(CSSStyleDeclaration* pageStyle)
{
if (m_isRevalidating)
return;
m_isRevalidating = true;
ensureFlatRules();
for (unsigned i = 0, size = m_flatRules.size(); i < size; ++i) {
CSSStyleRule* parsedRule = m_flatRules.at(i);
if (parsedRule->style() == pageStyle) {
if (parsedRule->style()->cssText() != pageStyle->cssText()) {
// Clear the disabled properties for the invalid style here.
m_inspectorStyles.remove(pageStyle);
setStyleText(pageStyle, pageStyle->cssText());
}
break;
}
}
m_isRevalidating = false;
}
bool InspectorStyleSheet::originalStyleSheetText(String* result) const
{
String text;
bool success = inlineStyleSheetText(&text);
if (!success)
success = resourceStyleSheetText(&text);
if (success)
*result = text;
return success;
}
bool InspectorStyleSheet::resourceStyleSheetText(String* result) const
{
if (m_origin == "user" || m_origin == "user-agent")
return false;
if (!m_pageStyleSheet || !ownerDocument())
return false;
String error;
InspectorResourceAgent::resourceContent(&error, ownerDocument()->frame(), m_pageStyleSheet->finalURL(), result);
return error.isEmpty();
}
bool InspectorStyleSheet::inlineStyleSheetText(String* result) const
{
if (!m_pageStyleSheet)
return false;
Node* ownerNode = m_pageStyleSheet->ownerNode();
if (!ownerNode || ownerNode->nodeType() != Node::ELEMENT_NODE)
return false;
Element* ownerElement = static_cast<Element*>(ownerNode);
if (ownerElement->tagName().lower() != "style")
return false;
*result = ownerElement->innerText();
return true;
}
PassRefPtr<InspectorArray> InspectorStyleSheet::buildArrayForRuleList(CSSRuleList* ruleList)
{
RefPtr<InspectorArray> result = InspectorArray::create();
if (!ruleList)
return result.release();
RefPtr<CSSRuleList> refRuleList = ruleList;
Vector<CSSStyleRule*> rules;
collectFlatRules(refRuleList, &rules);
for (unsigned i = 0, size = rules.size(); i < size; ++i)
result->pushObject(buildObjectForRule(rules.at(i)));
return result.release();
}
void InspectorStyleSheet::fixUnparsedPropertyRanges(CSSRuleSourceData* ruleData, const String& styleSheetText)
{
Vector<CSSPropertySourceData>& propertyData = ruleData->styleSourceData->propertyData;
unsigned size = propertyData.size();
if (!size)
return;
unsigned styleStart = ruleData->styleSourceData->styleBodyRange.start;
const UChar* characters = styleSheetText.characters();
CSSPropertySourceData* nextData = &(propertyData.at(0));
for (unsigned i = 0; i < size; ++i) {
CSSPropertySourceData* currentData = nextData;
nextData = i < size - 1 ? &(propertyData.at(i + 1)) : 0;
if (currentData->parsedOk)
continue;
if (currentData->range.end > 0 && characters[styleStart + currentData->range.end - 1] == ';')
continue;
unsigned propertyEndInStyleSheet;
if (!nextData)
propertyEndInStyleSheet = ruleData->styleSourceData->styleBodyRange.end - 1;
else
propertyEndInStyleSheet = styleStart + nextData->range.start - 1;
while (isHTMLSpace(characters[propertyEndInStyleSheet]))
--propertyEndInStyleSheet;
// propertyEndInStyleSheet points at the last property text character.
unsigned newPropertyEnd = propertyEndInStyleSheet - styleStart + 1; // Exclusive of the last property text character.
if (currentData->range.end != newPropertyEnd) {
currentData->range.end = newPropertyEnd;
unsigned valueStartInStyleSheet = styleStart + currentData->range.start + currentData->name.length();
while (valueStartInStyleSheet < propertyEndInStyleSheet && characters[valueStartInStyleSheet] != ':')
++valueStartInStyleSheet;
if (valueStartInStyleSheet < propertyEndInStyleSheet)
++valueStartInStyleSheet; // Shift past the ':'.
while (valueStartInStyleSheet < propertyEndInStyleSheet && isHTMLSpace(characters[valueStartInStyleSheet]))
++valueStartInStyleSheet;
// Need to exclude the trailing ';' from the property value.
currentData->value = styleSheetText.substring(valueStartInStyleSheet, propertyEndInStyleSheet - valueStartInStyleSheet + (characters[propertyEndInStyleSheet] == ';' ? 0 : 1));
}
}
}
void InspectorStyleSheet::collectFlatRules(PassRefPtr<CSSRuleList> ruleList, Vector<CSSStyleRule*>* result)
{
if (!ruleList)
return;
for (unsigned i = 0, size = ruleList->length(); i < size; ++i) {
CSSRule* rule = ruleList->item(i);
CSSStyleRule* styleRule = InspectorCSSAgent::asCSSStyleRule(rule);
if (styleRule)
result->append(styleRule);
else {
RefPtr<CSSRuleList> childRuleList = asCSSRuleList(rule);
if (childRuleList)
collectFlatRules(childRuleList, result);
}
}
}
PassRefPtr<InspectorStyleSheetForInlineStyle> InspectorStyleSheetForInlineStyle::create(const String& id, PassRefPtr<Element> element, const String& origin)
{
return adoptRef(new InspectorStyleSheetForInlineStyle(id, element, origin));
}
InspectorStyleSheetForInlineStyle::InspectorStyleSheetForInlineStyle(const String& id, PassRefPtr<Element> element, const String& origin)
: InspectorStyleSheet(id, 0, origin, "")
, m_element(element)
, m_ruleSourceData(0)
{
ASSERT(m_element);
m_inspectorStyle = InspectorStyle::create(InspectorCSSId(id, 0), inlineStyle(), this);
m_styleText = m_element->isStyledElement() ? m_element->getAttribute("style").string() : String();
}
void InspectorStyleSheetForInlineStyle::didModifyElementAttribute()
{
String newStyleText = elementStyleText();
bool shouldDropSourceData = false;
if (m_element->isStyledElement() && m_element->style() != m_inspectorStyle->cssStyle()) {
m_inspectorStyle = InspectorStyle::create(InspectorCSSId(id(), 0), inlineStyle(), this);
shouldDropSourceData = true;
}
if (newStyleText != m_styleText) {
m_styleText = newStyleText;
shouldDropSourceData = true;
}
if (shouldDropSourceData)
m_ruleSourceData.clear();
}
bool InspectorStyleSheetForInlineStyle::text(String* result) const
{
*result = m_styleText;
return true;
}
bool InspectorStyleSheetForInlineStyle::setStyleText(CSSStyleDeclaration* style, const String& text)
{
ASSERT_UNUSED(style, style == inlineStyle());
ExceptionCode ec = 0;
m_element->setAttribute("style", text, ec);
m_styleText = text;
m_ruleSourceData.clear();
return !ec;
}
Document* InspectorStyleSheetForInlineStyle::ownerDocument() const
{
return m_element->document();
}
bool InspectorStyleSheetForInlineStyle::ensureParsedDataReady()
{
// The "style" property value can get changed indirectly, e.g. via element.style.borderWidth = "2px".
const String& currentStyleText = elementStyleText();
if (m_styleText != currentStyleText) {
m_ruleSourceData.clear();
m_styleText = currentStyleText;
}
if (m_ruleSourceData)
return true;
m_ruleSourceData = CSSRuleSourceData::create();
RefPtr<CSSStyleSourceData> sourceData = CSSStyleSourceData::create();
bool success = getStyleAttributeRanges(&sourceData);
if (!success)
return false;
m_ruleSourceData->styleSourceData = sourceData.release();
return true;
}
PassRefPtr<InspectorStyle> InspectorStyleSheetForInlineStyle::inspectorStyleForId(const InspectorCSSId& id)
{
ASSERT_UNUSED(id, !id.ordinal());
return m_inspectorStyle;
}
CSSStyleDeclaration* InspectorStyleSheetForInlineStyle::inlineStyle() const
{
return m_element->style();
}
const String& InspectorStyleSheetForInlineStyle::elementStyleText() const
{
return m_element->getAttribute("style").string();
}
bool InspectorStyleSheetForInlineStyle::getStyleAttributeRanges(RefPtr<CSSStyleSourceData>* result)
{
if (!m_element->isStyledElement())
return false;
if (m_styleText.isEmpty()) {
(*result)->styleBodyRange.start = 0;
(*result)->styleBodyRange.end = 0;
return true;
}
RefPtr<CSSMutableStyleDeclaration> tempDeclaration = CSSMutableStyleDeclaration::create();
CSSParser p;
p.parseDeclaration(tempDeclaration.get(), m_styleText, result);
return true;
}
} // namespace WebCore
#endif // ENABLE(INSPECTOR)