/* * 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 #include #include class ParsedStyleSheet { public: typedef Vector > 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); bool hasSourceData() const { return m_sourceData; } RefPtr ruleSourceDataAt(unsigned index) const; private: // StyleSheet constructed while parsing m_text. WebCore::CSSStyleSheet* m_parserOutput; String m_text; bool m_hasText; OwnPtr 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) { m_sourceData = sourceData; } RefPtr ParsedStyleSheet::ruleSourceDataAt(unsigned index) const { if (!hasSourceData() || index >= m_sourceData->size()) return 0; return m_sourceData->at(index); } namespace WebCore { static PassRefPtr buildSourceRangeObject(const SourceRange& range) { RefPtr result = InspectorObject::create(); result->setNumber("start", range.start); result->setNumber("end", range.end); return result.release(); } static PassRefPtr asCSSRuleList(StyleBase* styleBase) { if (!styleBase) return 0; if (styleBase->isCSSStyleSheet()) return CSSRuleList::create(static_cast(styleBase), true); if (styleBase->isRule()) { unsigned ruleType = static_cast(styleBase)->type(); RefPtr result = 0; switch (ruleType) { case CSSRule::MEDIA_RULE: result = static_cast(styleBase)->cssRules(); break; case CSSRule::WEBKIT_KEYFRAMES_RULE: result = static_cast(styleBase)->cssRules(); break; case CSSRule::IMPORT_RULE: case CSSRule::PAGE_RULE: default: return 0; } return result.release(); } return 0; } PassRefPtr InspectorStyle::create(const InspectorCSSId& styleId, PassRefPtr style, InspectorStyleSheet* parentStyleSheet) { return adoptRef(new InspectorStyle(styleId, style, parentStyleSheet)); } InspectorStyle::InspectorStyle(const InspectorCSSId& styleId, PassRefPtr style, InspectorStyleSheet* parentStyleSheet) : m_styleId(styleId) , m_style(style) , m_parentStyleSheet(parentStyleSheet) { ASSERT(m_style); } InspectorStyle::~InspectorStyle() { } PassRefPtr InspectorStyle::buildObjectForStyle() const { RefPtr 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 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 allProperties; populateAllProperties(&allProperties); unsigned propertyStart = 0; // Need to initialize to make the compiler happy. long propertyLengthDelta; if (propertyText.stripWhiteSpace().length()) { RefPtr tempMutableStyle = CSSMutableStyleDeclaration::create(); CSSParser p; RefPtr sourceData = CSSStyleSourceData::create(); p.parseDeclaration(tempMutableStyle.get(), propertyText + " -webkit-boguz-propertee: none", &sourceData); Vector& 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 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 sourceData = m_parentStyleSheet->ruleSourceDataFor(m_style.get()); if (!sourceData) { *errorString = "Internal error: No source data for the style found"; return false; } Vector 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& 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 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& 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& 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* result) const { HashSet foundShorthands; HashSet sourcePropertyNames; unsigned disabledIndex = 0; unsigned disabledLength = m_disabledProperties.size(); InspectorStyleProperty disabledProperty; if (disabledIndex < disabledLength) disabledProperty = m_disabledProperties.at(disabledIndex); RefPtr sourceData = (m_parentStyleSheet && m_parentStyleSheet->ensureParsedDataReady()) ? m_parentStyleSheet->ruleSourceDataFor(m_style.get()) : 0; Vector* sourcePropertyData = sourceData ? &(sourceData->styleSourceData->propertyData) : 0; if (sourcePropertyData) { String styleDeclaration; bool isStyleTextKnown = styleText(&styleDeclaration); ASSERT_UNUSED(isStyleTextKnown, isStyleTextKnown); for (Vector::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 properties; populateAllProperties(&properties); RefPtr propertiesObject = InspectorArray::create(); RefPtr shorthandEntries = InspectorArray::create(); HashMap > propertyNameToPreviousActiveProperty; HashSet foundShorthands; for (Vector::iterator it = properties.begin(), itEnd = properties.end(); it != itEnd; ++it) { const CSSPropertySourceData& propertyEntry = it->sourceData; const String& name = propertyEntry.name; RefPtr 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 >::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 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 InspectorStyle::longhandProperties(const String& shorthandProperty) const { Vector properties; HashSet 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::create(const String& id, PassRefPtr pageStyleSheet, const String& origin, const String& documentURL) { return adoptRef(new InspectorStyleSheet(id, pageStyleSheet, origin, documentURL)); } InspectorStyleSheet::InspectorStyleSheet(const String& id, PassRefPtr 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 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 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 InspectorStyleSheet::buildObjectForStyleSheet() { CSSStyleSheet* styleSheet = pageStyleSheet(); if (!styleSheet) return 0; RefPtr result = InspectorObject::create(); result->setString("styleSheetId", id()); RefPtr cssRuleList = CSSRuleList::create(styleSheet, true); RefPtr 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 InspectorStyleSheet::buildObjectForStyleSheetInfo() { CSSStyleSheet* styleSheet = pageStyleSheet(); if (!styleSheet) return 0; RefPtr 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 InspectorStyleSheet::buildObjectForRule(CSSStyleRule* rule) { CSSStyleSheet* styleSheet = pageStyleSheet(); if (!styleSheet) return 0; RefPtr 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 sourceData; if (ensureParsedDataReady()) sourceData = ruleSourceDataFor(rule->style()); if (sourceData) { RefPtr selectorRange = InspectorObject::create(); selectorRange->setNumber("start", sourceData->selectorListRange.start); selectorRange->setNumber("end", sourceData->selectorListRange.end); result->setObject("selectorRange", selectorRange.release()); } return result.release(); } PassRefPtr InspectorStyleSheet::buildObjectForStyle(CSSStyleDeclaration* style) { RefPtr sourceData; if (ensureParsedDataReady()) sourceData = ruleSourceDataFor(style); InspectorCSSId id = ruleOrStyleId(style); if (id.isEmpty()) { RefPtr bogusStyle = InspectorObject::create(); bogusStyle->setArray("cssProperties", InspectorArray::create()); bogusStyle->setObject("shorthandValues", InspectorObject::create()); bogusStyle->setObject("properties", InspectorObject::create()); return bogusStyle.release(); } RefPtr inspectorStyle = inspectorStyleForId(id); RefPtr 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 = 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 = 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 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::create(id, style, this); return inspectorStyle.release(); } return it->second; } void InspectorStyleSheet::rememberInspectorStyle(RefPtr 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 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 newStyleSheet = CSSStyleSheet::create(); CSSParser p; StyleRuleRangeMap ruleRangeMap; p.parseSheet(newStyleSheet.get(), m_parsedStyleSheet->text(), 0, &ruleRangeMap); OwnPtr rangesVector(new ParsedStyleSheet::SourceData()); Vector rules; RefPtr 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 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(ownerNode); if (ownerElement->tagName().lower() != "style") return false; *result = ownerElement->innerText(); return true; } PassRefPtr InspectorStyleSheet::buildArrayForRuleList(CSSRuleList* ruleList) { RefPtr result = InspectorArray::create(); if (!ruleList) return result.release(); RefPtr refRuleList = ruleList; Vector 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& 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 ruleList, Vector* 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 childRuleList = asCSSRuleList(rule); if (childRuleList) collectFlatRules(childRuleList, result); } } } PassRefPtr InspectorStyleSheetForInlineStyle::create(const String& id, PassRefPtr element, const String& origin) { return adoptRef(new InspectorStyleSheetForInlineStyle(id, element, origin)); } InspectorStyleSheetForInlineStyle::InspectorStyleSheetForInlineStyle(const String& id, PassRefPtr 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 sourceData = CSSStyleSourceData::create(); bool success = getStyleAttributeRanges(&sourceData); if (!success) return false; m_ruleSourceData->styleSourceData = sourceData.release(); return true; } PassRefPtr 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* result) { if (!m_element->isStyledElement()) return false; if (m_styleText.isEmpty()) { (*result)->styleBodyRange.start = 0; (*result)->styleBodyRange.end = 0; return true; } RefPtr tempDeclaration = CSSMutableStyleDeclaration::create(); CSSParser p; p.parseDeclaration(tempDeclaration.get(), m_styleText, result); return true; } } // namespace WebCore #endif // ENABLE(INSPECTOR)