/* 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/dom/InspectorUtils.h" #include "AnchorPositioningUtils.h" #include "ChildIterator.h" #include "Units.h" #include "gfxTextRun.h" #include "inLayoutUtils.h" #include "mozilla/DeclarationBlock.h" #include "mozilla/EventStateManager.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/RefPtr.h" #include "mozilla/RelativeLuminanceUtils.h" #include "mozilla/ScrollContainerFrame.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoCSSParser.h" #include "mozilla/ServoStyleRuleMap.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/CSSBinding.h" #include "mozilla/dom/CSSKeyframesRule.h" #include "mozilla/dom/CSSStylePropertiesBinding.h" #include "mozilla/dom/CSSStyleRule.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/CharacterData.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/Highlight.h" #include "mozilla/dom/HighlightRegistry.h" #include "mozilla/dom/InspectorFontFace.h" #include "mozilla/dom/InspectorUtilsBinding.h" #include "mozilla/dom/LinkStyle.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/gfx/Matrix.h" #include "nsArray.h" #include "nsAtom.h" #include "nsBlockFrame.h" #include "nsCSSProps.h" #include "nsCSSValue.h" #include "nsColor.h" #include "nsComputedDOMStyle.h" #include "nsContentList.h" #include "nsFieldSetFrame.h" #include "nsGlobalWindowInner.h" #include "nsGridContainerFrame.h" #include "nsIContentInlines.h" #include "nsIFrameInlines.h" #include "nsLayoutUtils.h" #include "nsNameSpaceManager.h" #include "nsPresContext.h" #include "nsQueryObject.h" #include "nsRange.h" #include "nsString.h" #include "nsStyleUtil.h" using namespace mozilla; using namespace mozilla::css; using namespace mozilla::dom; namespace mozilla::dom { static nsPresContext* EnsureSafeToHandOutRules(Element& aElement) { Document* doc = aElement.GetComposedDoc(); if (!doc) { return nullptr; } const PresShell* presShell = doc->GetPresShell(); if (!presShell) { return nullptr; } nsPresContext* presContext = presShell->GetPresContext(); if (!presContext) { return nullptr; } presContext->EnsureSafeToHandOutCSSRules(); return presContext; } static already_AddRefed GetCleanComputedStyleForElement( dom::Element* aElement, const PseudoStyleRequest& aPseudo) { MOZ_ASSERT(aElement); nsPresContext* pc = EnsureSafeToHandOutRules(*aElement); if (!pc) { return nullptr; } return nsComputedDOMStyle::GetComputedStyle(aElement, aPseudo); } /* static */ void InspectorUtils::GetAllStyleSheets(GlobalObject& aGlobalObject, Document& aDocument, bool aDocumentOnly, nsTArray>& aResult) { // Get the agent, then user and finally xbl sheets in the style set. PresShell* presShell = aDocument.GetPresShell(); nsTHashSet sheetSet; if (presShell) { ServoStyleSet* styleSet = presShell->StyleSet(); if (!aDocumentOnly) { const StyleOrigin kOrigins[] = {StyleOrigin::UserAgent, StyleOrigin::User}; for (const auto origin : kOrigins) { for (size_t i = 0, count = styleSet->SheetCount(origin); i < count; i++) { aResult.AppendElement(styleSet->SheetAt(origin, i)); } } } AutoTArray nonDocumentSheets; styleSet->AppendAllNonDocumentAuthorSheets(nonDocumentSheets); // The non-document stylesheet array can have duplicates due to adopted // stylesheets. nsTHashSet sheetSet; for (StyleSheet* sheet : nonDocumentSheets) { if (sheetSet.EnsureInserted(sheet)) { aResult.AppendElement(sheet); } } } // Get the document sheets. for (size_t i = 0; i < aDocument.SheetCount(); i++) { aResult.AppendElement(aDocument.SheetAt(i)); } for (auto& sheet : aDocument.AdoptedStyleSheets()) { if (sheetSet.EnsureInserted(sheet)) { aResult.AppendElement(sheet); } } } 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 \n return true; } /* static */ nsINode* InspectorUtils::GetParentForNode(nsINode& aNode, bool aShowingAnonymousContent) { if (nsINode* parent = aNode.GetParentNode()) { return parent; } if (aNode.IsDocument()) { return inLayoutUtils::GetContainerFor(*aNode.AsDocument()); } if (aShowingAnonymousContent) { if (auto* frag = DocumentFragment::FromNode(aNode)) { // This deals with shadow roots and HTMLTemplateElement.content. return frag->GetHost(); } } return nullptr; } /* static */ void InspectorUtils::GetChildrenForNode(nsINode& aNode, bool aShowingAnonymousContent, bool aIncludeAssignedNodes, bool aIncludeSubdocuments, nsTArray>& aResult) { if (aIncludeSubdocuments) { if (auto* doc = inLayoutUtils::GetSubDocumentFor(&aNode)) { aResult.AppendElement(doc); // XXX Do we really want to early-return? return; } } if (!aShowingAnonymousContent || !aNode.IsContent()) { for (nsINode* child = aNode.GetFirstChild(); child; child = child->GetNextSibling()) { aResult.AppendElement(child); } return; } if (auto* tmpl = HTMLTemplateElement::FromNode(aNode)) { aResult.AppendElement(tmpl->Content()); // XXX Do we really want to early-return? return; } if (auto* element = Element::FromNode(aNode)) { if (auto* shadow = element->GetShadowRoot()) { aResult.AppendElement(shadow); } } nsIContent* parent = aNode.AsContent(); if (auto* node = nsLayoutUtils::GetBackdropPseudo(parent)) { aResult.AppendElement(node); } if (auto* node = nsLayoutUtils::GetMarkerPseudo(parent)) { aResult.AppendElement(node); } if (auto* node = nsLayoutUtils::GetBeforePseudo(parent)) { aResult.AppendElement(node); } if (aIncludeAssignedNodes) { if (auto* slot = HTMLSlotElement::FromNode(aNode)) { for (nsINode* node : slot->AssignedNodes()) { aResult.AppendElement(node); } } } for (nsIContent* node = parent->GetFirstChild(); node; node = node->GetNextSibling()) { aResult.AppendElement(node); } AutoTArray anonKids; nsContentUtils::AppendNativeAnonymousChildren(parent, anonKids, nsIContent::eAllChildren); for (nsIContent* node : anonKids) { aResult.AppendElement(node); } if (auto* node = nsLayoutUtils::GetAfterPseudo(parent)) { aResult.AppendElement(node); } } class ReadOnlyInspectorDeclaration final : public nsDOMCSSDeclaration { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ReadOnlyInspectorDeclaration) explicit ReadOnlyInspectorDeclaration(const StyleLockedDeclarationBlock* aRaw) : mRaw(aRaw) {} nsINode* GetAssociatedNode() const final { return nullptr; } nsISupports* GetParentObject() const final { return nullptr; } void GetPropertyValue(const nsACString& aPropName, nsACString& aValue) final { Servo_DeclarationBlock_GetPropertyValue(mRaw, &aPropName, &aValue); } void GetPropertyValue(NonCustomCSSPropertyId aId, nsACString& aValue) final { Servo_DeclarationBlock_GetPropertyValueByNonCustomId(mRaw, aId, &aValue); } bool HasLonghandProperty(const nsACString& aPropName) final { return Servo_DeclarationBlock_HasLonghandProperty(mRaw, &aPropName); } void IndexedGetter(uint32_t aIndex, bool& aFound, nsACString& aPropName) final { aFound = Servo_DeclarationBlock_GetNthProperty(mRaw, aIndex, &aPropName); } void RemoveProperty(const nsACString& aPropertyName, nsACString& aValue, ErrorResult& aRv) final { aRv.ThrowInvalidModificationError("Can't mutate this declaration"); } void SetProperty(const nsACString& aPropertyName, const nsACString& aValue, const nsACString& aPriority, nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) final { aRv.ThrowInvalidModificationError("Can't mutate this declaration"); } void SetPropertyValue(NonCustomCSSPropertyId aId, const nsACString& aValue, nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) final { aRv.ThrowInvalidModificationError("Can't mutate this declaration"); } void SetCssText(const nsACString& aString, nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) final { aRv.ThrowInvalidModificationError("Can't mutate this declaration"); } void GetCssText(nsACString& aString) final { Servo_DeclarationBlock_GetCssText(mRaw, &aString); } uint32_t Length() final { return Servo_DeclarationBlock_Count(mRaw); } void GetPropertyPriority(const nsACString& aPropName, nsACString& aPriority) final { if (Servo_DeclarationBlock_GetPropertyIsImportant(mRaw, &aPropName)) { aPriority.AssignLiteral("important"); } } css::Rule* GetParentRule() final { return nullptr; } JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) final { return CSSStyleProperties_Binding::Wrap(aCx, this, aGivenProto); } // These ones are a bit sad, but matches e.g. nsComputedDOMStyle. nsresult SetCSSDeclaration(DeclarationBlock* aDecl, MutationClosureData*) final { MOZ_CRASH("called ReadOnlyInspectorDeclaration::SetCSSDeclaration"); } DeclarationBlock* GetOrCreateCSSDeclaration(Operation, DeclarationBlock**) override { MOZ_CRASH("called ReadOnlyInspectorDeclaration::GetOrCreateCSSDeclaration"); } ParsingEnvironment GetParsingEnvironment(nsIPrincipal*) const final { MOZ_CRASH("called ReadOnlyInspectorDeclaration::GetParsingEnvironment"); } private: ~ReadOnlyInspectorDeclaration() = default; RefPtr mRaw; }; NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadOnlyInspectorDeclaration) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsICSSDeclaration) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadOnlyInspectorDeclaration) NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadOnlyInspectorDeclaration) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(ReadOnlyInspectorDeclaration) static void GetCSSRulesFromComputedValues( Element& aElement, const ComputedStyle* aComputedStyle, bool aWithStartingStyle, nsTArray& aResult) { const PresShell* presShell = aElement.OwnerDoc()->GetPresShell(); if (!presShell) { return; } AutoTArray rawDecls; Servo_ComputedValues_GetMatchingDeclarations(aComputedStyle, aWithStartingStyle, &rawDecls); AutoTArray maps; { ServoStyleSet* styleSet = presShell->StyleSet(); ServoStyleRuleMap* map = styleSet->StyleRuleMap(); maps.AppendElement(map); } // Now shadow DOM stuff... if (auto* shadow = aElement.GetShadowRoot()) { maps.AppendElement(&shadow->ServoStyleRuleMap()); } // Now NAC: for (auto* el = aElement.GetClosestNativeAnonymousSubtreeRootParentOrHost(); el; el = el->GetClosestNativeAnonymousSubtreeRootParentOrHost()) { if (auto* shadow = el->GetShadowRoot()) { maps.AppendElement(&shadow->ServoStyleRuleMap()); } } for (auto* shadow = aElement.GetContainingShadow(); shadow; shadow = shadow->Host()->GetContainingShadow()) { maps.AppendElement(&shadow->ServoStyleRuleMap()); } // Rules from the assigned slot. for (auto* slot = aElement.GetAssignedSlot(); slot; slot = slot->GetAssignedSlot()) { if (auto* shadow = slot->GetContainingShadow()) { maps.AppendElement(&shadow->ServoStyleRuleMap()); } } // Find matching rules in the table. for (const StyleMatchingDeclarationBlock& block : Reversed(rawDecls)) { bool found = false; for (ServoStyleRuleMap* map : maps) { if (css::Rule* rule = map->Lookup(block.block)) { aResult.AppendElement()->SetAsCSSRule() = rule; found = true; break; } } if (!found) { auto& declaration = aResult.AppendElement()->SetAsInspectorDeclaration(); declaration.mStyle = OwningNonNull( *new ReadOnlyInspectorDeclaration(block.block)); declaration.mDeclarationOrigin = [&] { switch (block.origin) { case StyleMatchingDeclarationBlockOrigin::Author: return DeclarationOrigin::Style_attribute; case StyleMatchingDeclarationBlockOrigin::User: MOZ_ASSERT_UNREACHABLE( "Where did this user agent declaration come from?"); return DeclarationOrigin::User; case StyleMatchingDeclarationBlockOrigin::UserAgent: return DeclarationOrigin::User_agent; case StyleMatchingDeclarationBlockOrigin::PositionFallback: return DeclarationOrigin::Position_fallback; case StyleMatchingDeclarationBlockOrigin::Animations: return DeclarationOrigin::Animations; case StyleMatchingDeclarationBlockOrigin::Transitions: return DeclarationOrigin::Transitions; case StyleMatchingDeclarationBlockOrigin::SMIL: return DeclarationOrigin::Smil; case StyleMatchingDeclarationBlockOrigin::PresHints: return DeclarationOrigin::Pres_hints; } MOZ_ASSERT_UNREACHABLE("Unkown origin?"); return DeclarationOrigin::Pres_hints; }(); } } } /* static */ void InspectorUtils::GetMatchingCSSRules( GlobalObject& aGlobalObject, Element& aElement, const nsAString& aPseudo, bool aIncludeVisitedStyle, bool aWithStartingStyle, nsTArray& aResult) { auto pseudo = PseudoStyleRequest::Parse(aPseudo); if (!pseudo) { return; } RefPtr computedStyle = GetCleanComputedStyleForElement(&aElement, *pseudo); 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; } if (aIncludeVisitedStyle) { if (const auto* styleIfVisited = computedStyle->GetStyleIfVisited()) { computedStyle = styleIfVisited; } } GetCSSRulesFromComputedValues(aElement, computedStyle, aWithStartingStyle, aResult); } /* static */ uint32_t InspectorUtils::GetRuleLine(GlobalObject& aGlobal, css::Rule& aRule) { uint32_t line = aRule.GetLineNumber(); if (StyleSheet* sheet = aRule.GetStyleSheet()) { if (auto* link = LinkStyle::FromNodeOrNull(sheet->GetOwnerNode())) { line += link->GetLineNumber(); } } return line; } /* static */ uint32_t InspectorUtils::GetRuleColumn(GlobalObject& aGlobal, css::Rule& aRule) { return aRule.GetColumnNumber(); } /* static */ uint32_t InspectorUtils::GetRelativeRuleLine(GlobalObject& aGlobal, css::Rule& aRule) { // Rule lines are 0-based, but inspector wants 1-based. return aRule.GetLineNumber() + 1; } void InspectorUtils::GetRuleIndex(GlobalObject& aGlobal, css::Rule& aRule, nsTArray& aResult) { css::Rule* currentRule = &aRule; do { css::Rule* parentRule = currentRule->GetParentRule(); dom::CSSRuleList* ruleList = nullptr; if (parentRule) { if (parentRule->IsGroupRule()) { ruleList = static_cast(parentRule)->CssRules(); } else if (parentRule->Type() == StyleCssRuleType::Keyframes) { ruleList = static_cast(parentRule)->CssRules(); } else { MOZ_ASSERT_UNREACHABLE("Unknown parent rule type?"); } } else if (StyleSheet* sheet = currentRule->GetStyleSheet()) { ruleList = sheet->GetCssRulesInternal(); } if (!ruleList) { return; } bool found = false; for (uint32_t i = 0, len = ruleList->Length(); i < len; ++i) { css::Rule* rule = ruleList->Item(i); if (currentRule == rule) { found = true; aResult.InsertElementAt(0, i); break; } } if (!found) { return; } currentRule = parentRule; } while (currentRule); } /* static */ bool InspectorUtils::HasRulesModifiedByCSSOM(GlobalObject& aGlobal, StyleSheet& aSheet) { return aSheet.HasModifiedRulesForDevtools(); } static uint32_t CollectAtRules(ServoCSSRuleList& aRuleList, Sequence>& aResult) { uint32_t len = aRuleList.Length(); uint32_t ruleCount = len; for (uint32_t i = 0; i < len; ++i) { css::Rule* rule = aRuleList.GetRule(i); // This collect rules we want to display in Devtools Style Editor toolbar. // When adding a new StyleCssRuleType, put it in the "default" list, and // file a new bug with // https://bugzilla.mozilla.org/enter_bug.cgi?product=DevTools&component=Style%20Editor&short_desc=Consider%20displaying%20new%20XXX%20rule%20type%20in%20at-rules%20sidebar // so the DevTools team gets notified and can decide if it should be // displayed. switch (rule->Type()) { case StyleCssRuleType::CustomMedia: case StyleCssRuleType::Media: case StyleCssRuleType::Supports: case StyleCssRuleType::LayerBlock: case StyleCssRuleType::PositionTry: case StyleCssRuleType::Property: case StyleCssRuleType::Container: { (void)aResult.AppendElement(OwningNonNull(*rule), fallible); break; } case StyleCssRuleType::Style: case StyleCssRuleType::Import: case StyleCssRuleType::Document: case StyleCssRuleType::LayerStatement: case StyleCssRuleType::FontFace: case StyleCssRuleType::Page: case StyleCssRuleType::Keyframes: case StyleCssRuleType::Keyframe: case StyleCssRuleType::Margin: case StyleCssRuleType::Namespace: case StyleCssRuleType::CounterStyle: case StyleCssRuleType::FontFeatureValues: case StyleCssRuleType::FontPaletteValues: case StyleCssRuleType::Scope: case StyleCssRuleType::StartingStyle: case StyleCssRuleType::NestedDeclarations: case StyleCssRuleType::AppearanceBase: case StyleCssRuleType::ViewTransition: break; } if (rule->IsGroupRule()) { ruleCount += CollectAtRules( *static_cast(rule)->CssRules(), aResult); } } return ruleCount; } void InspectorUtils::GetStyleSheetRuleCountAndAtRules( GlobalObject& aGlobal, StyleSheet& aSheet, InspectorStyleSheetRuleCountAndAtRulesResult& aResult) { aResult.mRuleCount = CollectAtRules(*aSheet.GetCssRulesInternal(), aResult.mAtRules); } /* static */ bool InspectorUtils::IsInheritedProperty(GlobalObject& aGlobalObject, Document& aDocument, const nsACString& aPropertyName) { return Servo_Property_IsInherited(aDocument.EnsureStyleSet().RawData(), &aPropertyName); } /* static */ void InspectorUtils::GetCSSPropertyNames(GlobalObject& aGlobalObject, const PropertyNamesOptions& aOptions, nsTArray& aResult) { CSSEnabledState enabledState = aOptions.mIncludeExperimentals ? CSSEnabledState::IgnoreEnabledState : CSSEnabledState::ForAllContent; auto appendProperty = [enabledState, &aResult](uint32_t prop) { NonCustomCSSPropertyId cssProp = NonCustomCSSPropertyId(prop); if (nsCSSProps::IsEnabled(cssProp, enabledState)) { aResult.AppendElement( NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(cssProp))); } }; uint32_t prop = 0; for (; prop < eCSSProperty_COUNT_no_shorthands; ++prop) { if (!nsCSSProps::PropHasFlags(NonCustomCSSPropertyId(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 InspectorUtils::GetCSSPropertyPrefs(GlobalObject& aGlobalObject, nsTArray& aResult) { for (const auto* src = nsCSSProps::kPropertyPrefTable; src->mPropId != eCSSProperty_UNKNOWN; src++) { PropertyPref& dest = *aResult.AppendElement(); dest.mName.Assign( NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(src->mPropId))); dest.mPref.AssignASCII(src->mPref); } } /* static */ void InspectorUtils::GetSubpropertiesForCSSProperty(GlobalObject& aGlobal, const nsACString& aProperty, nsTArray& aResult, ErrorResult& aRv) { NonCustomCSSPropertyId propertyId = nsCSSProps::LookupProperty(aProperty); if (propertyId == eCSSProperty_UNKNOWN) { aRv.Throw(NS_ERROR_FAILURE); return; } if (propertyId == eCSSPropertyExtra_variable) { aResult.AppendElement(NS_ConvertUTF8toUTF16(aProperty)); return; } if (!nsCSSProps::IsShorthand(propertyId)) { nsString* name = aResult.AppendElement(); CopyASCIItoUTF16(nsCSSProps::GetStringValue(propertyId), *name); return; } for (const NonCustomCSSPropertyId* props = nsCSSProps::SubpropertyEntryFor(propertyId); *props != eCSSProperty_UNKNOWN; ++props) { nsString* name = aResult.AppendElement(); CopyASCIItoUTF16(nsCSSProps::GetStringValue(*props), *name); } } /* static */ bool InspectorUtils::CssPropertyIsShorthand(GlobalObject& aGlobalObject, const nsACString& aProperty, ErrorResult& aRv) { bool found; bool isShorthand = Servo_Property_IsShorthand(&aProperty, &found); if (!found) { aRv.Throw(NS_ERROR_FAILURE); } return isShorthand; } // This should match the constants in specified_value_info.rs // // Once we can use bitflags in consts, we can also cbindgen that and use them // here instead. static uint8_t ToServoCssType(InspectorPropertyType aType) { switch (aType) { case InspectorPropertyType::Color: return 1; case InspectorPropertyType::Gradient: return 1 << 1; case InspectorPropertyType::Timing_function: return 1 << 2; default: MOZ_ASSERT_UNREACHABLE("Unknown property type?"); return 0; } } bool InspectorUtils::Supports(GlobalObject&, const nsACString& aDeclaration, const SupportsOptions& aOptions) { return Servo_CSSSupports(&aDeclaration, aOptions.mUserAgent, aOptions.mChrome, aOptions.mQuirks); } bool InspectorUtils::CssPropertySupportsType(GlobalObject& aGlobalObject, const nsACString& aProperty, InspectorPropertyType aType, ErrorResult& aRv) { bool found; bool result = Servo_Property_SupportsType(&aProperty, ToServoCssType(aType), &found); if (!found) { aRv.Throw(NS_ERROR_FAILURE); return false; } return result; } /* static */ void InspectorUtils::GetCSSValuesForProperty(GlobalObject& aGlobalObject, const nsACString& aProperty, nsTArray& aResult, ErrorResult& aRv) { bool found; Servo_Property_GetCSSValuesForProperty(&aProperty, &found, &aResult); if (!found) { aRv.Throw(NS_ERROR_FAILURE); } } /* static */ void InspectorUtils::GetCSSWideKeywords(GlobalObject& aGlobalObject, nsTArray& aResult) { Servo_Property_GetCSSWideKeywords(&aResult); } /* static */ void InspectorUtils::RgbToColorName(GlobalObject&, uint8_t aR, uint8_t aG, uint8_t aB, nsACString& aColorName) { Servo_SlowRgbToColorName(aR, aG, aB, &aColorName); } void InspectorUtils::RgbToNearestColorName(GlobalObject&, float aR, float aG, float aB, InspectorNearestColor& aResult) { bool exact = Servo_SlowRgbToNearestColorName( aR, aG, aB, StyleColorSpace::Srgb, &aResult.mColorName); aResult.mExact = exact; } /* static */ void InspectorUtils::RgbToHsv(GlobalObject&, float aR, float aG, float aB, nsTArray& aResult) { StyleAbsoluteColor input{ .components = StyleColorComponents{aR, aG, aB}, .alpha = 1.0, .color_space = StyleColorSpace::Srgb, }; StyleAbsoluteColor result = input.ToColorSpace(StyleColorSpace::Hwb); float h = result.components._0 / 360.0f; float v = 1 - result.components._2 / 100.0f; float s = 0; if (v != 0.0) { s = 1 - (result.components._1 / 100.0f) / v; } aResult = {h, s, v}; } /* static */ void InspectorUtils::HsvToRgb(GlobalObject&, float aH, float aS, float aV, nsTArray& aResult) { float h = aH * 360; float w = aV * (1 - aS) * 100; float b = (1 - aV) * 100; StyleAbsoluteColor input{ .components = StyleColorComponents{h, w, b}, .alpha = 1.0, .color_space = StyleColorSpace::Hwb, }; StyleAbsoluteColor result = input.ToColorSpace(StyleColorSpace::Srgb); aResult = {result.components._0, result.components._1, result.components._2}; } /* static */ float InspectorUtils::RelativeLuminance(GlobalObject&, float aR, float aG, float aB) { return RelativeLuminanceUtils::Compute(aR, aG, aB); } /* static */ void InspectorUtils::ColorToRGBA(GlobalObject& aGlobal, const nsACString& aColorString, Nullable& aResult) { const auto* styleData = [&]() -> const StylePerDocumentStyleData* { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { return nullptr; } auto* win = global->GetAsInnerWindow(); if (!win) { return nullptr; } Document* doc = win->GetExtantDoc(); if (!doc) { return nullptr; } PresShell* ps = doc->GetPresShell(); if (!ps) { return nullptr; } return ps->StyleSet()->RawData(); }(); nscolor color = NS_RGB(0, 0, 0); if (!ServoCSSParser::ComputeColor(styleData, 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 */ void InspectorUtils::ColorTo(GlobalObject&, const nsACString& aFromColor, const nsACString& aToColorSpace, Nullable& aResult) { nsCString resultColor; nsTArray resultComponents; bool resultAdjusted = false; if (!ServoCSSParser::ColorTo(aFromColor, aToColorSpace, &resultColor, &resultComponents, &resultAdjusted)) { aResult.SetNull(); return; } auto& result = aResult.SetValue(); result.mColor.AssignASCII(resultColor); result.mComponents = std::move(resultComponents); result.mAdjusted = resultAdjusted; } /* static */ bool InspectorUtils::IsValidCSSColor(GlobalObject& aGlobalObject, const nsACString& aColorString) { return ServoCSSParser::IsValidCSSColor(aColorString); } /* static */ bool InspectorUtils::SetContentState(GlobalObject& aGlobalObject, Element& aElement, uint64_t aState, ErrorResult& aRv) { RefPtr esm = inLayoutUtils::GetEventStateManagerFor(aElement); ElementState state(aState); if (!esm || !EventStateManager::ManagesState(state)) { aRv.Throw(NS_ERROR_INVALID_ARG); return false; } return esm->SetContentState(&aElement, state); } /* static */ bool InspectorUtils::RemoveContentState(GlobalObject& aGlobalObject, Element& aElement, uint64_t aState, bool aClearActiveDocument, ErrorResult& aRv) { RefPtr esm = inLayoutUtils::GetEventStateManagerFor(aElement); ElementState state(aState); if (!esm || !EventStateManager::ManagesState(state)) { aRv.Throw(NS_ERROR_INVALID_ARG); return false; } bool result = esm->SetContentState(nullptr, state); if (aClearActiveDocument && state == ElementState::ACTIVE) { EventStateManager* activeESM = static_cast( 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 ElementState return aElement.State().GetInternalValue(); } /* static */ void InspectorUtils::GetUsedFontFaces(GlobalObject& aGlobalObject, nsRange& aRange, uint32_t aMaxRanges, bool aSkipCollapsedWhitespace, nsLayoutUtils::UsedFontFaceList& aResult, ErrorResult& aRv) { nsresult rv = aRange.GetUsedFontFaces(aResult, aMaxRanges, aSkipCollapsedWhitespace); if (NS_FAILED(rv)) { aRv.Throw(rv); } } static ElementState GetStatesForPseudoClass(const nsAString& aStatePseudo) { if (aStatePseudo.IsEmpty() || aStatePseudo[0] != u':') { return ElementState(); } NS_ConvertUTF16toUTF8 statePseudo(Substring(aStatePseudo, 1)); return ElementState(Servo_PseudoClass_GetStates(&statePseudo)); } /* static */ void InspectorUtils::GetCSSPseudoElementNames(GlobalObject& aGlobalObject, nsTArray& aResult) { const auto kPseudoCount = static_cast(PseudoStyleType::MAX); for (size_t i = 0; i < kPseudoCount; ++i) { PseudoStyleType type = static_cast(i); if (type == PseudoStyleType::NotPseudo || !Servo_PseudoStyleType_EnabledForAllContent(type)) { continue; } auto& string = *aResult.AppendElement(); // Use two semi-colons (though internally we use one). string.Append(u':'); const nsStaticAtom* atom = PseudoStyle::GetAtom(type); string.Append(nsDependentAtomString(atom)); } } /* static */ void InspectorUtils::AddPseudoClassLock(GlobalObject& aGlobalObject, Element& aElement, const nsAString& aPseudoClass, bool aEnabled) { ElementState state = GetStatesForPseudoClass(aPseudoClass); if (state.IsEmpty()) { return; } aElement.LockStyleStates(state, aEnabled); } /* static */ void InspectorUtils::RemovePseudoClassLock(GlobalObject& aGlobal, Element& aElement, const nsAString& aPseudoClass) { ElementState state = GetStatesForPseudoClass(aPseudoClass); if (state.IsEmpty()) { return; } aElement.UnlockStyleStates(state); } /* static */ bool InspectorUtils::HasPseudoClassLock(GlobalObject& aGlobalObject, Element& aElement, const nsAString& aPseudoClass) { ElementState state = GetStatesForPseudoClass(aPseudoClass); if (state.IsEmpty()) { return false; } ElementState 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 nsACString& aInput, ErrorResult& aRv) { aSheet.ReparseSheet(aInput, aRv); } bool InspectorUtils::IsCustomElementName(GlobalObject&, const nsAString& aName, const nsAString& aNamespaceURI) { if (aName.IsEmpty()) { return false; } int32_t namespaceID; nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, namespaceID); RefPtr nameElt = NS_Atomize(aName); return nsContentUtils::IsCustomElementName(nameElt, namespaceID); } bool InspectorUtils::IsElementThemed(GlobalObject&, Element& aElement) { // IsThemed will check if the native theme supports the widget using // ThemeSupportsWidget which in turn will check that the widget is not // already styled by content through nsNativeTheme::IsWidgetStyled. We // assume that if the native theme styles the widget and the author did not // override the appropriate styles, the theme will provide focus styling. nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames); return frame && frame->IsThemed(); } bool InspectorUtils::IsUsedColorSchemeDark(GlobalObject&, Element& aElement) { nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames); return frame && LookAndFeel::ColorSchemeForFrame(frame) == ColorScheme::Dark; } Element* InspectorUtils::ContainingBlockOf(GlobalObject&, Element& aElement) { nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames); if (!frame) { return nullptr; } nsIFrame* cb = frame->GetContainingBlock(); if (!cb) { return nullptr; } return Element::FromNodeOrNull(cb->GetContent()); } bool InspectorUtils::IsBlockContainer(GlobalObject&, Element& aElement) { nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames); if (!frame) { return false; } if (frame->IsBlockFrameOrSubclass()) { return true; } // ScrollContainerFrame::GetContentInsertionFrame() jumps across the block and // returns the ruby inside (because it calls GetContentInsertionFrame on its // own). So we have to account for that here. if (auto* sc = frame->GetScrollTargetFrame()) { if (sc->GetScrolledFrame()->IsBlockFrameOrSubclass()) { return true; } } if (nsIFrame* inner = frame->GetContentInsertionFrame()) { if (inner->IsBlockFrameOrSubclass()) { return true; } } return false; } void InspectorUtils::GetBlockLineCounts(GlobalObject& aGlobal, Element& aElement, Nullable>& aResult) { nsBlockFrame* block = do_QueryFrame(aElement.GetPrimaryFrame(FlushType::Layout)); if (!block) { aResult.SetNull(); return; } // If CSS columns were specified on the actual block element (rather than an // ancestor block, GetPrimaryFrame will return its ColumnSetWrapperFrame, and // we need to drill down to the actual block that contains the lines. if (block->IsColumnSetWrapperFrame()) { nsIFrame* firstChild = block->PrincipalChildList().FirstChild(); if (!firstChild->IsColumnSetFrame()) { aResult.SetNull(); return; } block = do_QueryFrame(firstChild->PrincipalChildList().FirstChild()); if (!block || block->GetContent() != &aElement) { aResult.SetNull(); return; } } nsTArray result; do { result.AppendElement(block->Lines().size()); block = static_cast(block->GetNextInFlow()); } while (block); aResult.SetValue(std::move(result)); } static bool FrameHasSpecifiedSize(const nsIFrame* aFrame) { auto wm = aFrame->GetWritingMode(); const nsStylePosition* stylePos = aFrame->StylePosition(); const auto anchorResolutionParams = AnchorPosResolutionParams::From(aFrame); return stylePos->ISize(wm, anchorResolutionParams)->IsLengthPercentage() || stylePos->BSize(wm, anchorResolutionParams)->IsLengthPercentage(); } static bool IsFrameOutsideOfAncestor(const nsIFrame* aFrame, const nsIFrame* aAncestorFrame, const nsRect& aAncestorRect) { nsRect frameRectInAncestorSpace = nsLayoutUtils::TransformFrameRectToAncestor( aFrame, aFrame->ScrollableOverflowRect(), RelativeTo{aAncestorFrame}, nullptr, nullptr, false, nullptr); // We use nsRect::SaturatingUnionEdges because it correctly handles the case // of a zero-width or zero-height frame, which we still want to consider as // contributing to the union. nsRect unionizedRect = frameRectInAncestorSpace.SaturatingUnionEdges(aAncestorRect); // If frameRectInAncestorSpace is inside aAncestorRect then union of // frameRectInAncestorSpace and aAncestorRect should be equal to aAncestorRect // hence if it is equal, then false should be returned. return !(unionizedRect == aAncestorRect); } static void AddOverflowingChildrenOfElement(const nsIFrame* aFrame, const nsIFrame* aAncestorFrame, const nsRect& aRect, nsSimpleContentList& aList) { MOZ_ASSERT(aFrame, "we assume the passed-in frame is non-null"); for (const auto& childList : aFrame->ChildLists()) { for (const nsIFrame* child : childList.mList) { // We want to identify if the child or any of its children have a // frame that is outside of aAncestorFrame. Ideally, child would have // a frame rect that encompasses all of its children, but this is not // guaranteed by the frame tree. So instead we first check other // conditions that indicate child is an interesting frame: // // 1) child has a specified size // 2) none of child's children are implicated // // If either of these conditions are true, we *then* check if child's // frame is outside of aAncestorFrame, and if so, we add child's content // to aList. if (FrameHasSpecifiedSize(child) && IsFrameOutsideOfAncestor(child, aAncestorFrame, aRect)) { aList.MaybeAppendElement(child->GetContent()); continue; } uint32_t currListLength = aList.Length(); AddOverflowingChildrenOfElement(child, aAncestorFrame, aRect, aList); // If child is a leaf node, length of aList should remain same after // calling AddOverflowingChildrenOfElement on it. if (currListLength == aList.Length() && IsFrameOutsideOfAncestor(child, aAncestorFrame, aRect)) { aList.MaybeAppendElement(child->GetContent()); } } } } already_AddRefed InspectorUtils::GetOverflowingChildrenOfElement( GlobalObject& aGlobal, Element& aElement) { auto list = MakeRefPtr(&aElement); const ScrollContainerFrame* scrollContainerFrame = aElement.GetScrollContainerFrame(); // Element must be a ScrollContainerFrame. if (!scrollContainerFrame) { return list.forget(); } auto scrollPortRect = scrollContainerFrame->GetScrollPortRect(); const nsIFrame* scrolledFrame = scrollContainerFrame->GetScrolledFrame(); AddOverflowingChildrenOfElement(scrolledFrame, scrollContainerFrame, scrollPortRect, *list); return list.forget(); } /* static */ void InspectorUtils::GetRegisteredCssHighlights(GlobalObject& aGlobalObject, Document& aDocument, bool aActiveOnly, nsTArray& aResult) { for (auto const& iter : aDocument.HighlightRegistry().HighlightsOrdered()) { const RefPtr& highlightName = iter.first(); const RefPtr& highlight = iter.second(); if (!aActiveOnly || highlight->Size() > 0) { aResult.AppendElement(highlightName->GetUTF16String()); } } } /* static */ void InspectorUtils::GetCSSRegisteredProperties( GlobalObject& aGlobalObject, Document& aDocument, nsTArray& aResult) { nsTArray result; ServoStyleSet& styleSet = aDocument.EnsureStyleSet(); // Update the rules before looking up @property rules. styleSet.UpdateStylistIfNeeded(); Servo_GetRegisteredCustomProperties(styleSet.RawData(), &result); for (const auto& propDef : result) { InspectorCSSPropertyDefinition& property = *aResult.AppendElement(); // Servo does not include the "--" prefix in the property definition name. // Add it back as it's easier for DevTools to handle them _with_ "--". property.mName.AssignLiteral("--"); property.mName.Append(nsAtomCString(propDef.name.AsAtom())); property.mSyntax.Append(propDef.syntax); property.mInherits = propDef.inherits; if (propDef.has_initial_value) { property.mInitialValue.Append(propDef.initial_value); } else { property.mInitialValue.SetIsVoid(true); } property.mFromJS = propDef.from_js; } } /* static */ void InspectorUtils::GetCSSRegisteredProperty( GlobalObject& aGlobalObject, Document& aDocument, const nsACString& aName, Nullable& aResult) { StylePropDef result{StyleAtom(NS_Atomize(aName))}; // Update the rules before looking up @property rules. ServoStyleSet& styleSet = aDocument.EnsureStyleSet(); styleSet.UpdateStylistIfNeeded(); if (!Servo_GetRegisteredCustomProperty(styleSet.RawData(), &aName, &result)) { aResult.SetNull(); return; } InspectorCSSPropertyDefinition& propDef = aResult.SetValue(); // Servo does not include the "--" prefix in the property definition name. // Add it back as it's easier for DevTools to handle them _with_ "--". propDef.mName.AssignLiteral("--"); propDef.mName.Append(nsAtomCString(result.name.AsAtom())); propDef.mSyntax.Append(result.syntax); propDef.mInherits = result.inherits; if (result.has_initial_value) { propDef.mInitialValue.Append(result.initial_value); } else { propDef.mInitialValue.SetIsVoid(true); } propDef.mFromJS = result.from_js; } /* static */ bool InspectorUtils::ValueMatchesSyntax(GlobalObject&, Document& aDocument, const nsACString& aValue, const nsACString& aSyntax) { return Servo_Value_Matches_Syntax(&aValue, &aSyntax, aDocument.DefaultStyleAttrURLData()); } /* static */ void InspectorUtils::GetRuleBodyText(GlobalObject&, const nsACString& aInitialText, nsACString& aBodyText) { Servo_GetRuleBodyText(&aInitialText, &aBodyText); } /* static */ void InspectorUtils::ReplaceBlockRuleBodyTextInStylesheet( GlobalObject&, const nsACString& aStyleSheetText, uint32_t aLine, uint32_t aColumn, const nsACString& aNewBodyText, nsACString& aNewStyleSheetText) { Servo_ReplaceBlockRuleBodyTextInStylesheetText( &aStyleSheetText, aLine, aColumn, &aNewBodyText, &aNewStyleSheetText); } void InspectorUtils::SetVerticalClipping(GlobalObject&, BrowsingContext* aContext, mozilla::CSSCoord aOffset) { MOZ_ASSERT(XRE_IsParentProcess()); if (!aContext) { return; } CanonicalBrowsingContext* canonical = aContext->Canonical(); if (!canonical) { return; } BrowserParent* parent = canonical->GetBrowserParent(); if (!parent) { return; } const LayoutDeviceToCSSScale scale = parent->GetLayoutDeviceToCSSScale(); const mozilla::LayoutDeviceCoord layoutOffset = aOffset / scale; const mozilla::ScreenCoord screenOffset = ViewAs( layoutOffset, PixelCastJustification::LayoutDeviceIsScreenForBounds); const mozilla::ScreenIntCoord offset = screenOffset.Rounded(); parent->DynamicToolbarOffsetChanged(offset); RefPtr widget = canonical->GetParentProcessWidgetContaining(); if (!widget) { return; } widget->DynamicToolbarOffsetChanged(offset); } void InspectorUtils::SetDynamicToolbarMaxHeight(GlobalObject&, BrowsingContext* aContext, mozilla::CSSCoord aHeight) { MOZ_ASSERT(XRE_IsParentProcess()); if (!aContext) { return; } CanonicalBrowsingContext* canonical = aContext->Canonical(); if (!canonical) { return; } BrowserParent* parent = canonical->GetBrowserParent(); if (!parent) { return; } const LayoutDeviceToCSSScale scale = parent->GetLayoutDeviceToCSSScale(); const mozilla::LayoutDeviceCoord layoutOffset = aHeight / scale; const mozilla::ScreenCoord screenOffset = ViewAs( layoutOffset, PixelCastJustification::LayoutDeviceIsScreenForBounds); const mozilla::ScreenIntCoord height = screenOffset.Rounded(); parent->DynamicToolbarMaxHeightChanged(height); } uint16_t InspectorUtils::GetGridContainerType(GlobalObject&, Element& aElement) { auto* f = nsGridContainerFrame::GetGridContainerFrame( aElement.GetPrimaryFrame(FlushType::Frames)); if (!f) { return InspectorUtils_Binding::GRID_NONE; } uint16_t result = InspectorUtils_Binding::GRID_CONTAINER; if (f->IsRowSubgrid()) { result |= InspectorUtils_Binding::GRID_SUBGRID_ROW; } if (f->IsColSubgrid()) { result |= InspectorUtils_Binding::GRID_SUBGRID_COL; } return result; } void InspectorUtils::GetAnchorFor(GlobalObject&, Element& aElement, const nsAString& aName, Nullable& aResult) { auto* frame = aElement.GetPrimaryFrame(FlushType::Frames); if (!frame || !frame->IsAbsolutelyPositioned()) { return; } nsIFrame* anchor = nullptr; RefPtr name; ScopedNameRef scopedName{nullptr, StyleCascadeLevel::Default()}; InspectorAnchorType type = InspectorAnchorType::Explicit; if (aName.IsEmpty()) { // Anchor name, otherwise implicit anchor. const auto& positionAnchor = frame->StylePosition()->mPositionAnchor; if (positionAnchor.value.IsIdent()) { scopedName = {positionAnchor.value.AsIdent().AsAtom(), positionAnchor.scope}; } else { auto implicit = AnchorPositioningUtils::GetAnchorPosImplicitAnchor(frame); anchor = implicit.mAnchorFrame; if (!anchor) { return; } switch (implicit.mKind) { case AnchorPositioningUtils::ImplicitAnchorKind::None: break; case AnchorPositioningUtils::ImplicitAnchorKind::Popover: type = InspectorAnchorType::Popover; break; case AnchorPositioningUtils::ImplicitAnchorKind::PseudoElement: type = InspectorAnchorType::Pseudo_element; break; } auto& result = aResult.SetValue(); result.mElement = *anchor->GetContent()->AsElement(); result.mType = type; return; } } else { // TODO(emilio): Allow looking up names from other trees. name = NS_Atomize(aName); scopedName = {name, StyleCascadeLevel::Default()}; } anchor = frame->PresShell()->GetAnchorPosAnchor(scopedName, frame); if (!anchor) { return; } auto& result = aResult.SetValue(); result.mElement = *anchor->GetContent()->AsElement(); result.mType = type; } /* static */ void InspectorUtils::GetAnchorNamesFor(GlobalObject& aGlobalObject, Element& aElement, nsTArray& aResult) { auto* frame = aElement.GetPrimaryFrame(FlushType::Frames); if (!frame || !frame->IsAbsolutelyPositioned()) { return; } frame->PresShell()->CollectAnchorNames(frame, aResult); } } // namespace mozilla::dom