layout/inspector/InspectorUtils.cpp
author Xidorn Quan <me@upsuper.org>
Thu, 26 Apr 2018 15:00:50 +1000
changeset 415778 47db02b5cdc975f919f5118f68520426d807ed49
parent 415776 344b5a9033f0c2243d63aad697058b9162e9df57
child 416005 995b14b30559aab3736e11bdc1591cf50349c03b
permissions -rw-r--r--
Bug 1456364 part 4 - Convert CSS_PROPERTY_* flags to a typed enum flags. r=emilio MozReview-Commit-ID: 4mLPe5cH80N

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/ArrayUtils.h"
#include "mozilla/EventStates.h"

#include "inLayoutUtils.h"

#include "nsArray.h"
#include "nsAutoPtr.h"
#include "nsIServiceManager.h"
#include "nsString.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsIContentInlines.h"
#include "nsIDOMElement.h"
#include "nsIDocument.h"
#include "nsIPresShell.h"
#include "nsIDOMDocument.h"
#include "nsIDOMWindow.h"
#include "nsXBLBinding.h"
#include "nsXBLPrototypeBinding.h"
#include "nsIMutableArray.h"
#include "nsBindingManager.h"
#include "ChildIterator.h"
#include "nsComputedDOMStyle.h"
#include "mozilla/EventStateManager.h"
#include "nsAtom.h"
#include "nsRange.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/dom/CharacterData.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/CSSLexer.h"
#include "mozilla/dom/InspectorUtilsBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsCSSProps.h"
#include "nsCSSValue.h"
#include "nsColor.h"
#include "mozilla/ServoStyleSet.h"
#include "nsStyleUtil.h"
#include "nsQueryObject.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleRule.h"
#include "mozilla/ServoStyleRuleMap.h"
#include "mozilla/ServoCSSParser.h"
#include "mozilla/dom/InspectorUtils.h"
#include "mozilla/dom/InspectorFontFace.h"

using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;

extern const char* const kCSSRawProperties[];

namespace mozilla {
namespace dom {

/* static */ void
InspectorUtils::GetAllStyleSheets(GlobalObject& aGlobalObject,
                                  nsIDocument& aDocument,
                                  bool aDocumentOnly,
                                  nsTArray<RefPtr<StyleSheet>>& aResult)
{
  // Get the agent, then user and finally xbl sheets in the style set.
  nsIPresShell* presShell = aDocument.GetShell();

  if (presShell) {
    ServoStyleSet* styleSet = presShell->StyleSet();

    if (!aDocumentOnly) {
      SheetType sheetType = SheetType::Agent;
      for (int32_t i = 0; i < styleSet->SheetCount(sheetType); i++) {
        aResult.AppendElement(styleSet->StyleSheetAt(sheetType, i));
      }
      sheetType = SheetType::User;
      for (int32_t i = 0; i < styleSet->SheetCount(sheetType); i++) {
        aResult.AppendElement(styleSet->StyleSheetAt(sheetType, i));
      }
    }

    AutoTArray<StyleSheet*, 32> xblSheetArray;
    styleSet->AppendAllNonDocumentAuthorSheets(xblSheetArray);

    // The XBL stylesheet array will quite often be full of duplicates. Cope:
    //
    // FIXME(emilio, bug 1454467): I think this is not true since bug 1452525.
    nsTHashtable<nsPtrHashKey<StyleSheet>> sheetSet;
    for (StyleSheet* sheet : xblSheetArray) {
      if (!sheetSet.Contains(sheet)) {
        sheetSet.PutEntry(sheet);
        aResult.AppendElement(sheet);
      }
    }
  }

  // Get the document sheets.
  for (size_t i = 0; i < aDocument.SheetCount(); i++) {
    aResult.AppendElement(aDocument.SheetAt(i));
  }
}

bool
InspectorUtils::IsIgnorableWhitespace(CharacterData& aDataNode)
{
  if (!aDataNode.TextIsOnlyWhitespace()) {
    return false;
  }

  // Okay.  We have only white space.  Let's check the white-space
  // property now and make sure that this isn't preformatted text...
  if (nsIFrame* frame = aDataNode.GetPrimaryFrame()) {
    return !frame->StyleText()->WhiteSpaceIsSignificant();
  }

  // empty inter-tag text node without frame, e.g., in between <table>\n<tr>
  return true;
}

/* static */ nsINode*
InspectorUtils::GetParentForNode(nsINode& aNode,
                                 bool aShowingAnonymousContent)
{
  // First do the special cases -- document nodes and anonymous content
  nsINode* parent = nullptr;

  if (aNode.IsDocument()) {
    parent = inLayoutUtils::GetContainerFor(*aNode.AsDocument());
  } else if (aShowingAnonymousContent) {
    if (aNode.IsContent()) {
      parent = aNode.AsContent()->GetFlattenedTreeParent();
    }
  }

  if (!parent) {
    // Ok, just get the normal DOM parent node
    return aNode.GetParentNode();
  }

  return parent;
}

/* static */ already_AddRefed<nsINodeList>
InspectorUtils::GetChildrenForNode(nsINode& aNode,
                                   bool aShowingAnonymousContent)
{
  nsCOMPtr<nsINodeList> kids;

  if (aShowingAnonymousContent) {
    if (aNode.IsContent()) {
      kids = aNode.AsContent()->GetChildren(nsIContent::eAllChildren);
    }
  }

  if (!kids) {
    kids = aNode.ChildNodes();
  }

  return kids.forget();
}

/* static */ void
InspectorUtils::GetCSSStyleRules(GlobalObject& aGlobalObject,
                                 Element& aElement,
                                 const nsAString& aPseudo,
                                 nsTArray<RefPtr<css::Rule>>& aResult)
{
  RefPtr<nsAtom> pseudoElt;
  if (!aPseudo.IsEmpty()) {
    pseudoElt = NS_Atomize(aPseudo);
  }

  RefPtr<ComputedStyle> computedStyle =
    GetCleanComputedStyleForElement(&aElement, pseudoElt);
  if (!computedStyle) {
    // This can fail for elements that are not in the document or
    // if the document they're in doesn't have a presshell.  Bail out.
    return;
  }


  nsIDocument* doc = aElement.OwnerDoc();
  nsIPresShell* shell = doc->GetShell();
  if (!shell) {
    return;
  }

  nsTArray<const RawServoStyleRule*> rawRuleList;
  Servo_ComputedValues_GetStyleRuleList(computedStyle, &rawRuleList);

  AutoTArray<ServoStyleRuleMap*, 1> maps;
  {
    ServoStyleSet* styleSet = shell->StyleSet();
    ServoStyleRuleMap* map = styleSet->StyleRuleMap();
    maps.AppendElement(map);
  }

  // Collect style rule maps for bindings.
  for (nsIContent* bindingContent = &aElement; bindingContent;
       bindingContent = bindingContent->GetBindingParent()) {
    for (nsXBLBinding* binding = bindingContent->GetXBLBinding();
         binding; binding = binding->GetBaseBinding()) {
      if (auto* map = binding->PrototypeBinding()->GetServoStyleRuleMap()) {
        maps.AppendElement(map);
      }
    }
    // Note that we intentionally don't cut off here, unlike when we
    // do styling, because even if style rules from parent binding
    // do not apply to the element directly in those cases, their
    // rules may still show up in the list we get above due to the
    // inheritance in cascading.
  }

  // Now shadow DOM stuff...
  if (auto* shadow = aElement.GetShadowRoot()) {
    maps.AppendElement(&shadow->ServoStyleRuleMap());
  }

  for (auto* shadow = aElement.GetContainingShadow();
       shadow;
       shadow = shadow->Host()->GetContainingShadow()) {
    maps.AppendElement(&shadow->ServoStyleRuleMap());
  }

  // Find matching rules in the table.
  for (const RawServoStyleRule* rawRule : Reversed(rawRuleList)) {
    ServoStyleRule* rule = nullptr;
    for (ServoStyleRuleMap* map : maps) {
      rule = map->Lookup(rawRule);
      if (rule) {
        break;
      }
    }
    if (rule) {
      aResult.AppendElement(rule);
    } else {
      MOZ_ASSERT_UNREACHABLE("We should be able to map a raw rule to a rule");
    }
  }
}

/* static */ uint32_t
InspectorUtils::GetRuleLine(GlobalObject& aGlobal, css::Rule& aRule)
{
  return aRule.GetLineNumber();
}

/* static */ uint32_t
InspectorUtils::GetRuleColumn(GlobalObject& aGlobal, css::Rule& aRule)
{
  return aRule.GetColumnNumber();
}

/* static */ uint32_t
InspectorUtils::GetRelativeRuleLine(GlobalObject& aGlobal, css::Rule& aRule)
{
  uint32_t lineNumber = aRule.GetLineNumber();

  // If aRule was parsed along with its stylesheet, then it will
  // have an absolute lineNumber that we need to remap to its
  // containing node. But if aRule was added via CSSOM after parsing,
  // then it has a sort-of relative line number already:
  // Gecko gives all rules a 0 lineNumber.
  // Servo gives the first line of a rule a 0 lineNumber, and then
  //   counts up from there.

  // The Servo behavior is arguably more correct, but harder to
  // interpret for purposes of deciding whether a lineNumber is
  // relative or absolute.

  // Since most of the time, inserted rules are single line and
  // therefore have 0 lineNumbers in both Gecko and Servo, we use
  // that to detect that a lineNumber is already relative.

  // There is one ugly edge case that we avoid: if an inserted rule
  // is multi-line, then Servo will give it 0+ lineNumbers. If we
  // do relative number mapping on those line numbers, we could get
  // negative underflow. So we check for underflow and instead report
  // a 0 lineNumber.
  StyleSheet* sheet = aRule.GetStyleSheet();
  if (sheet && lineNumber != 0) {
    nsINode* owningNode = sheet->GetOwnerNode();
    if (owningNode) {
      nsCOMPtr<nsIStyleSheetLinkingElement> link =
        do_QueryInterface(owningNode);
      if (link) {
        // Check for underflow, which is one indication that we're
        // trying to remap an already relative lineNumber.
        uint32_t linkLineIndex0 = link->GetLineNumber() - 1;
        if (linkLineIndex0 > lineNumber ) {
          lineNumber = 0;
        } else {
          lineNumber -= linkLineIndex0;
        }
      }
    }
  }

  return lineNumber;
}

/* static */ bool
InspectorUtils::HasRulesModifiedByCSSOM(GlobalObject& aGlobal, StyleSheet& aSheet)
{
  return aSheet.HasModifiedRules();
}

/* static */ CSSLexer*
InspectorUtils::GetCSSLexer(GlobalObject& aGlobal, const nsAString& aText)
{
  return new CSSLexer(aText);
}

/* static */ uint32_t
InspectorUtils::GetSelectorCount(GlobalObject& aGlobal,
                                 BindingStyleRule& aRule)
{
  return aRule.GetSelectorCount();
}

/* static */ void
InspectorUtils::GetSelectorText(GlobalObject& aGlobal,
                                BindingStyleRule& aRule,
                                uint32_t aSelectorIndex,
                                nsString& aText,
                                ErrorResult& aRv)
{
  aRv = aRule.GetSelectorText(aSelectorIndex, aText);
}

/* static */ uint64_t
InspectorUtils::GetSpecificity(GlobalObject& aGlobal,
                               BindingStyleRule& aRule,
                               uint32_t aSelectorIndex,
                               ErrorResult& aRv)
{
  uint64_t s;
  aRv = aRule.GetSpecificity(aSelectorIndex, &s);
  return s;
}

/* static */ bool
InspectorUtils::SelectorMatchesElement(GlobalObject& aGlobalObject,
                                       Element& aElement,
                                       BindingStyleRule& aRule,
                                       uint32_t aSelectorIndex,
                                       const nsAString& aPseudo,
                                       ErrorResult& aRv)
{
  bool result = false;
  aRv = aRule.SelectorMatchesElement(&aElement, aSelectorIndex, aPseudo,
                                     &result);
  return result;
}

/* static */ bool
InspectorUtils::IsInheritedProperty(GlobalObject& aGlobalObject,
                                    const nsAString& aPropertyName)
{
  NS_ConvertUTF16toUTF8 propName(aPropertyName);
  return Servo_Property_IsInherited(&propName);
}

/* static */ void
InspectorUtils::GetCSSPropertyNames(GlobalObject& aGlobalObject,
                                    const PropertyNamesOptions& aOptions,
                                    nsTArray<nsString>& aResult)
{
  CSSEnabledState enabledState = aOptions.mIncludeExperimentals
    ? CSSEnabledState::eIgnoreEnabledState
    : CSSEnabledState::eForAllContent;

  auto appendProperty = [enabledState, &aResult](uint32_t prop) {
    nsCSSPropertyID cssProp = nsCSSPropertyID(prop);
    if (nsCSSProps::IsEnabled(cssProp, enabledState)) {
      nsDependentCString name(kCSSRawProperties[prop]);
      aResult.AppendElement(NS_ConvertASCIItoUTF16(name));
    }
  };

  uint32_t prop = 0;
  for ( ; prop < eCSSProperty_COUNT_no_shorthands; ++prop) {
    if (!nsCSSProps::PropHasFlags(nsCSSPropertyID(prop),
                                  CSSPropFlags::Inaccessible)) {
      appendProperty(prop);
    }
  }

  if (aOptions.mIncludeShorthands) {
    for ( ; prop < eCSSProperty_COUNT; ++prop) {
      appendProperty(prop);
    }
  }

  if (aOptions.mIncludeAliases) {
    for (prop = eCSSProperty_COUNT; prop < eCSSProperty_COUNT_with_aliases; ++prop) {
      appendProperty(prop);
    }
  }
}

static void InsertNoDuplicates(nsTArray<nsString>& aArray,
                               const nsAString& aString)
{
  size_t i = aArray.IndexOfFirstElementGt(aString);
  if (i > 0 && aArray[i-1].Equals(aString)) {
    return;
  }
  aArray.InsertElementAt(i, aString);
}

static void GetKeywordsForProperty(const nsCSSPropertyID aProperty,
                                   nsTArray<nsString>& aArray)
{
  const nsCSSProps::KTableEntry* keywordTable;
  if (nsCSSProps::IsShorthand(aProperty)) {
    if (aProperty == eCSSProperty_font) {
      keywordTable = nsCSSProps::kFontKTable;
    } else {
      // Other shorthand props have no keywords.
      return;
    }
  } else {
    keywordTable = nsCSSProps::kKeywordTableTable[aProperty];
    // Special cases where nsCSSPropList.h doesn't hold the table.
    if (keywordTable == nullptr) {
      if (aProperty == eCSSProperty_clip_path) {
        keywordTable = nsCSSProps::kClipPathGeometryBoxKTable;
      }
    }
  }

  if (keywordTable) {
    for (size_t i = 0; !keywordTable[i].IsSentinel(); ++i) {
      nsCSSKeyword word = keywordTable[i].mKeyword;

      // These are extra -moz values which are added while rebuilding
      // the properties db. These values are not relevant and are not
      // documented on MDN, so filter these out
      // eCSSKeyword_UNKNOWN is ignored because it indicates an
      // invalid entry; but can still be seen in a table, see bug 1430616.
      if (word != eCSSKeyword__moz_zoom_in && word != eCSSKeyword__moz_zoom_out &&
          word != eCSSKeyword__moz_grab && word != eCSSKeyword__moz_grabbing &&
          word != eCSSKeyword_UNKNOWN) {
          InsertNoDuplicates(aArray,
                  NS_ConvertASCIItoUTF16(nsCSSKeywords::GetStringValue(word)));
      }
    }
  }

  // More special cases.
  if (aProperty == eCSSProperty_clip_path) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("circle"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("ellipse"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("inset"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("polygon"));
  } else if (aProperty == eCSSProperty_clip) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("rect"));
  } else if (aProperty == eCSSProperty_list_style_type) {
    int32_t length;
    const char* const* values = nsCSSProps::GetListStyleTypes(&length);
    for (int32_t i = 0; i < length; ++i) {
      InsertNoDuplicates(aArray, NS_ConvertASCIItoUTF16(values[i]));
    }
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("symbols"));
  }
}

static void GetColorsForProperty(const uint32_t aParserVariant,
                                 nsTArray<nsString>& aArray)
{
  if (aParserVariant & VARIANT_COLOR) {
    // GetKeywordsForProperty and GetOtherValuesForProperty assume aArray is sorted,
    // and if aArray is not empty here, then it's not going to be sorted coming out.
    MOZ_ASSERT(aArray.Length() == 0);
    size_t size;
    const char * const *allColorNames = NS_AllColorNames(&size);
    nsString* utf16Names = aArray.AppendElements(size);
    for (size_t i = 0; i < size; i++) {
      utf16Names[i].AssignASCII(allColorNames[i]);
    }
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("currentColor"));
  }
}

static void GetOtherValuesForProperty(const uint32_t aParserVariant,
                                      nsTArray<nsString>& aArray)
{
  if (aParserVariant & VARIANT_AUTO) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("auto"));
  }
  if (aParserVariant & VARIANT_NORMAL) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("normal"));
  }
  if(aParserVariant & VARIANT_ALL) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("all"));
  }
  if (aParserVariant & VARIANT_NONE) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("none"));
  }
  if (aParserVariant & VARIANT_ELEMENT) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-element"));
  }
  if (aParserVariant & VARIANT_IMAGE_RECT) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-image-rect"));
  }
  if (aParserVariant & VARIANT_COLOR) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("rgb"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("hsl"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("rgba"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("hsla"));
  }
  if (aParserVariant & VARIANT_TIMING_FUNCTION) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("cubic-bezier"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("steps"));
  }
  if (aParserVariant & VARIANT_CALC) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("calc"));
  }
  if (aParserVariant & VARIANT_URL) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("url"));
  }
  if (aParserVariant & VARIANT_GRADIENT) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("linear-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("radial-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("repeating-linear-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("repeating-radial-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-linear-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-radial-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-repeating-linear-gradient"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-repeating-radial-gradient"));
  }
  if (aParserVariant & VARIANT_ATTR) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("attr"));
  }
  if (aParserVariant & VARIANT_COUNTER) {
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("counter"));
    InsertNoDuplicates(aArray, NS_LITERAL_STRING("counters"));
  }
}

/* static */ void
InspectorUtils::GetSubpropertiesForCSSProperty(GlobalObject& aGlobal,
                                               const nsAString& aProperty,
                                               nsTArray<nsString>& aResult,
                                               ErrorResult& aRv)
{
  nsCSSPropertyID propertyID =
    nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);

  if (propertyID == eCSSProperty_UNKNOWN) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  if (propertyID == eCSSPropertyExtra_variable) {
    aResult.AppendElement(aProperty);
    return;
  }

  if (!nsCSSProps::IsShorthand(propertyID)) {
    nsString* name = aResult.AppendElement();
    CopyASCIItoUTF16(nsCSSProps::GetStringValue(propertyID), *name);
    return;
  }

  for (const nsCSSPropertyID* props = nsCSSProps::SubpropertyEntryFor(propertyID);
       *props != eCSSProperty_UNKNOWN; ++props) {
    nsString* name = aResult.AppendElement();
    CopyASCIItoUTF16(nsCSSProps::GetStringValue(*props), *name);
  }
}

/* static */ bool
InspectorUtils::CssPropertyIsShorthand(GlobalObject& aGlobalObject,
                                       const nsAString& aProperty,
                                       ErrorResult& aRv)
{
  NS_ConvertUTF16toUTF8 prop(aProperty);
  bool found;
  bool isShorthand = Servo_Property_IsShorthand(&prop, &found);
  if (!found) {
    aRv.Throw(NS_ERROR_FAILURE);
  }
  return isShorthand;
}

bool
InspectorUtils::CssPropertySupportsType(GlobalObject& aGlobalObject,
                                        const nsAString& aProperty,
                                        uint32_t aType,
                                        ErrorResult& aRv)
{
  NS_ConvertUTF16toUTF8 property(aProperty);
  bool found;
  bool result = Servo_Property_SupportsType(&property, aType, &found);
  if (!found) {
    aRv.Throw(NS_ERROR_FAILURE);
    return false;
  }
  return result;
}

/* static */ void
InspectorUtils::GetCSSValuesForProperty(GlobalObject& aGlobalObject,
                                        const nsAString& aProperty,
                                        nsTArray<nsString>& aResult,
                                        ErrorResult& aRv)
{
  nsCSSPropertyID propertyID = nsCSSProps::
    LookupProperty(aProperty, CSSEnabledState::eForAllContent);
  if (propertyID == eCSSProperty_UNKNOWN) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  // We start collecting the values, BUT colors need to go in first, because aResult
  // needs to stay sorted, and the colors are sorted, so we just append them.
  if (propertyID == eCSSPropertyExtra_variable) {
    // No other values we can report.
  } else if (!nsCSSProps::IsShorthand(propertyID)) {
    // Property is longhand.
    uint32_t propertyParserVariant = nsCSSProps::ParserVariant(propertyID);
    // Get colors first.
    GetColorsForProperty(propertyParserVariant, aResult);
    GetKeywordsForProperty(propertyID, aResult);
    GetOtherValuesForProperty(propertyParserVariant, aResult);
  } else if (propertyID == eCSSProperty_all) {
    // We don't want to pick up everything from gAllSubpropTable, so
    // special-case this here.
  } else {
    // Property is shorthand.
    CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subproperty, propertyID,
                                         CSSEnabledState::eForAllContent) {
      // Get colors (once) first.
      uint32_t propertyParserVariant = nsCSSProps::ParserVariant(*subproperty);
      if (propertyParserVariant & VARIANT_COLOR) {
        GetColorsForProperty(propertyParserVariant, aResult);
        break;
      }
    }
    // Some shorthands may have keywords not available in subproperties.
    GetKeywordsForProperty(propertyID, aResult);
    CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subproperty, propertyID,
                                         CSSEnabledState::eForAllContent) {
      uint32_t propertyParserVariant = nsCSSProps::ParserVariant(*subproperty);
      GetKeywordsForProperty(*subproperty, aResult);
      GetOtherValuesForProperty(propertyParserVariant, aResult);
    }
  }
  // All CSS properties take initial, inherit and unset.
  InsertNoDuplicates(aResult, NS_LITERAL_STRING("initial"));
  InsertNoDuplicates(aResult, NS_LITERAL_STRING("inherit"));
  InsertNoDuplicates(aResult, NS_LITERAL_STRING("unset"));
}

/* static */ void
InspectorUtils::RgbToColorName(GlobalObject& aGlobalObject,
                               uint8_t aR, uint8_t aG, uint8_t aB,
                               nsAString& aColorName,
                               ErrorResult& aRv)
{
  const char* color = NS_RGBToColorName(NS_RGB(aR, aG, aB));
  if (!color) {
    aColorName.Truncate();
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return;
  }

  aColorName.AssignASCII(color);
}

/* static */ void
InspectorUtils::ColorToRGBA(GlobalObject& aGlobalObject,
                            const nsAString& aColorString,
                            Nullable<InspectorRGBATuple>& aResult)
{
  nscolor color = NS_RGB(0, 0, 0);

  if (!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), aColorString,
                                    &color)) {
    aResult.SetNull();
    return;
  }

  InspectorRGBATuple& tuple = aResult.SetValue();
  tuple.mR = NS_GET_R(color);
  tuple.mG = NS_GET_G(color);
  tuple.mB = NS_GET_B(color);
  tuple.mA = nsStyleUtil::ColorComponentToFloat(NS_GET_A(color));
}

/* static */ bool
InspectorUtils::IsValidCSSColor(GlobalObject& aGlobalObject,
                                const nsAString& aColorString)
{
  return ServoCSSParser::IsValidCSSColor(aColorString);
}

void
InspectorUtils::GetBindingURLs(GlobalObject& aGlobalObject,
                               Element& aElement,
                               nsTArray<nsString>& aResult)
{
  nsXBLBinding* binding = aElement.GetXBLBinding();

  while (binding) {
    nsCString spec;
    nsCOMPtr<nsIURI> bindingURI = binding->PrototypeBinding()->BindingURI();
    bindingURI->GetSpec(spec);
    nsString* resultURI = aResult.AppendElement();
    CopyASCIItoUTF16(spec, *resultURI);
    binding = binding->GetBaseBinding();
  }
}

/* static */ bool
InspectorUtils::SetContentState(GlobalObject& aGlobalObject,
                                Element& aElement,
                                uint64_t aState,
                                ErrorResult& aRv)
{
  RefPtr<EventStateManager> esm =
    inLayoutUtils::GetEventStateManagerFor(aElement);
  if (!esm) {
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return false;
  }

  return esm->SetContentState(&aElement, EventStates(aState));
}

/* static */ bool
InspectorUtils::RemoveContentState(GlobalObject& aGlobalObject,
                                   Element& aElement,
                                   uint64_t aState,
                                   bool aClearActiveDocument,
                                   ErrorResult& aRv)
{
  RefPtr<EventStateManager> esm =
    inLayoutUtils::GetEventStateManagerFor(aElement);
  if (!esm) {
    aRv.Throw(NS_ERROR_INVALID_ARG);
    return false;
  }

  bool result = esm->SetContentState(nullptr, EventStates(aState));

  if (aClearActiveDocument && EventStates(aState) == NS_EVENT_STATE_ACTIVE) {
    EventStateManager* activeESM = static_cast<EventStateManager*>(
      EventStateManager::GetActiveEventStateManager());
    if (activeESM == esm) {
      EventStateManager::ClearGlobalActiveContent(nullptr);
    }
  }

  return result;
}

/* static */ uint64_t
InspectorUtils::GetContentState(GlobalObject& aGlobalObject,
                                Element& aElement)
{
  // NOTE: if this method is removed,
  // please remove GetInternalValue from EventStates
  return aElement.State().GetInternalValue();
}

/* static */ already_AddRefed<ComputedStyle>
InspectorUtils::GetCleanComputedStyleForElement(dom::Element* aElement,
                                               nsAtom* aPseudo)
{
  MOZ_ASSERT(aElement);

  nsIDocument* doc = aElement->GetComposedDoc();
  if (!doc) {
    return nullptr;
  }

  nsIPresShell *presShell = doc->GetShell();
  if (!presShell) {
    return nullptr;
  }

  nsPresContext *presContext = presShell->GetPresContext();
  if (!presContext) {
    return nullptr;
  }

  presContext->EnsureSafeToHandOutCSSRules();

  return nsComputedDOMStyle::GetComputedStyle(aElement, aPseudo);
}

/* static */ void
InspectorUtils::GetUsedFontFaces(GlobalObject& aGlobalObject,
                                 nsRange& aRange,
                                 uint32_t aMaxRanges,
                                 nsTArray<nsAutoPtr<InspectorFontFace>>& aResult,
                                 ErrorResult& aRv)
{
  nsresult rv = aRange.GetUsedFontFaces(aResult, aMaxRanges);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

static EventStates
GetStatesForPseudoClass(const nsAString& aStatePseudo)
{
  if (aStatePseudo.IsEmpty() || aStatePseudo[0] != u':') {
    return EventStates();
  }
  NS_ConvertUTF16toUTF8 statePseudo(Substring(aStatePseudo, 1));
  return EventStates(Servo_PseudoClass_GetStates(&statePseudo));
}

/* static */ void
InspectorUtils::GetCSSPseudoElementNames(GlobalObject& aGlobalObject,
                                         nsTArray<nsString>& aResult)
{
  const CSSPseudoElementTypeBase pseudoCount =
    static_cast<CSSPseudoElementTypeBase>(CSSPseudoElementType::Count);
  for (CSSPseudoElementTypeBase i = 0; i < pseudoCount; ++i) {
    CSSPseudoElementType type = static_cast<CSSPseudoElementType>(i);
    if (nsCSSPseudoElements::IsEnabled(type, CSSEnabledState::eForAllContent)) {
      nsAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type);
      aResult.AppendElement(nsDependentAtomString(atom));
    }
  }
}

/* static */ void
InspectorUtils::AddPseudoClassLock(GlobalObject& aGlobalObject,
                                   Element& aElement,
                                   const nsAString& aPseudoClass,
                                   bool aEnabled)
{
  EventStates state = GetStatesForPseudoClass(aPseudoClass);
  if (state.IsEmpty()) {
    return;
  }

  aElement.LockStyleStates(state, aEnabled);
}

/* static */ void
InspectorUtils::RemovePseudoClassLock(GlobalObject& aGlobal,
                                      Element& aElement,
                                      const nsAString& aPseudoClass)
{
  EventStates state = GetStatesForPseudoClass(aPseudoClass);
  if (state.IsEmpty()) {
    return;
  }

  aElement.UnlockStyleStates(state);
}

/* static */ bool
InspectorUtils::HasPseudoClassLock(GlobalObject& aGlobalObject,
                                   Element& aElement,
                                   const nsAString& aPseudoClass)
{
  EventStates state = GetStatesForPseudoClass(aPseudoClass);
  if (state.IsEmpty()) {
    return false;
  }

  EventStates locks = aElement.LockedStyleStates().mLocks;
  return locks.HasAllStates(state);
}

/* static */ void
InspectorUtils::ClearPseudoClassLocks(GlobalObject& aGlobalObject,
                                      Element& aElement)
{
  aElement.ClearStyleStateLocks();
}

/* static */ void
InspectorUtils::ParseStyleSheet(GlobalObject& aGlobalObject,
                                StyleSheet& aSheet,
                                const nsAString& aInput,
                                ErrorResult& aRv)
{

  RefPtr<ServoStyleSheet> servoSheet = aSheet.AsServo();
  aRv = servoSheet->ReparseSheet(aInput);
}

void
InspectorUtils::ScrollElementIntoView(GlobalObject& aGlobalObject,
                                      Element& aElement)
{
  nsIPresShell* presShell = aElement.OwnerDoc()->GetShell();
  if (!presShell) {
    return;
  }

  presShell->ScrollContentIntoView(&aElement,
                                   nsIPresShell::ScrollAxis(),
                                   nsIPresShell::ScrollAxis(),
                                   nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
}

} // namespace dom
} // namespace mozilla