text: /*
   * 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(unsigned index, const String& propertyText, bool overwrite)
  {
      ASSERT(m_parentStyleSheet);
      if (!m_parentStyleSheet->ensureParsedDataReady())
          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)
              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")
              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)
                  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)
              return false;
          String text;
          bool success = styleText(&text);
          if (!success)
              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(unsigned index, bool disable)
  {
      ASSERT(m_parentStyleSheet);
      if (!m_parentStyleSheet->ensureParsedDataReady())
          return false; // Can toggle only source-based properties.
      RefPtr<CSSRuleSourceData> sourceData = m_parentStyleSheet->ruleSourceDataFor(m_style.get());
      if (!sourceData)
          return false; // No source data for the style.
  
      Vector<InspectorStyleProperty> allProperties;
      populateAllProperties(&allProperties);
      if (index >= allProperties.size())
          return false; // Outside of property range.
  
      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);
          }
      }
  
      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))
              continue;
  
          sourcePropertyNames.add(name);
          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(const InspectorCSSId& id, unsigned propertyIndex, const String& text, bool overwrite)
  {
      RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
      if (!inspectorStyle)
          return false;
  
      return inspectorStyle->setPropertyText(propertyIndex, text, overwrite);
  }
  
  bool InspectorStyleSheet::toggleProperty(const InspectorCSSId& id, unsigned propertyIndex, bool disable)
  {
      RefPtr<InspectorStyle> inspectorStyle = inspectorStyleForId(id);
      if (!inspectorStyle)
          return false;
  
      bool success = inspectorStyle->toggleProperty(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)
  