/* -*- 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 "gfxTextRun.h" #include "nsArray.h" #include "nsString.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" #include "ChildIterator.h" #include "nsComputedDOMStyle.h" #include "mozilla/EventStateManager.h" #include "nsAtom.h" #include "nsPresContext.h" #include "nsRange.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/dom/CharacterData.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/CSSStyleRule.h" #include "mozilla/dom/InspectorUtilsBinding.h" #include "mozilla/dom/LinkStyle.h" #include "mozilla/dom/ToJSValue.h" #include "nsCSSProps.h" #include "nsCSSValue.h" #include "nsColor.h" #include "mozilla/ServoStyleSet.h" #include "nsLayoutUtils.h" #include "nsStyleUtil.h" #include "nsQueryObject.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoStyleRuleMap.h" #include "mozilla/ServoCSSParser.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/dom/InspectorUtils.h" #include "mozilla/dom/InspectorFontFace.h" using namespace mozilla; using namespace mozilla::css; using namespace mozilla::dom; namespace mozilla { namespace dom { /* 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(); 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't have duplicates right now, but it // could once we include adopted stylesheets. nsTHashtable> 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)); } // FIXME(emilio, bug 1617948): This doesn't deal with adopted stylesheets, and // it should. It should also handle duplicates correctly when it does, see // above. } 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) { // 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 InspectorUtils::GetChildrenForNode( nsINode& aNode, bool aShowingAnonymousContent) { nsCOMPtr 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, bool aIncludeVisitedStyle, nsTArray>& aResult) { RefPtr pseudoElt; if (!aPseudo.IsEmpty()) { pseudoElt = NS_Atomize(aPseudo); } RefPtr 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; } if (aIncludeVisitedStyle) { if (ComputedStyle* styleIfVisited = computedStyle->GetStyleIfVisited()) { computedStyle = styleIfVisited; } } Document* doc = aElement.OwnerDoc(); PresShell* presShell = doc->GetPresShell(); if (!presShell) { return; } nsTArray rawRuleList; Servo_ComputedValues_GetStyleRuleList(computedStyle, &rawRuleList); 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()); } 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 RawServoStyleRule* rawRule : Reversed(rawRuleList)) { CSSStyleRule* rule = nullptr; for (ServoStyleRuleMap* map : maps) { rule = map->Lookup(rawRule); if (rule) { break; } } if (rule) { aResult.AppendElement(rule); } else { #ifdef DEBUG nsAutoCString str; fprintf(stderr, "%s\n", str.get()); Servo_StyleRule_Debug(rawRule, &str); MOZ_CRASH_UNSAFE_PRINTF( "We should be able to map a raw rule to a rule: %s\n", str.get()); #endif } } } /* 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) { if (auto* link = LinkStyle::FromNodeOrNull(sheet->GetOwnerNode())) { // 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.HasModifiedRulesForDevtools(); } /* 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, bool aRelevantLinkVisited, ErrorResult& aRv) { bool result = false; aRv = aRule.SelectorMatchesElement(&aElement, aSelectorIndex, aPseudo, aRelevantLinkVisited, &result); return result; } /* static */ bool InspectorUtils::IsInheritedProperty(GlobalObject& aGlobalObject, const nsACString& aPropertyName) { return Servo_Property_IsInherited(&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) { nsCSSPropertyID cssProp = nsCSSPropertyID(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(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 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) { nsCSSPropertyID 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 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 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::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::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 nsACString& aColorString, Nullable& 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 nsACString& aColorString) { return ServoCSSParser::IsValidCSSColor(aColorString); } /* static */ bool InspectorUtils::SetContentState(GlobalObject& aGlobalObject, Element& aElement, uint64_t aState, ErrorResult& aRv) { RefPtr esm = inLayoutUtils::GetEventStateManagerFor(aElement); EventStates 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); EventStates state(aState); if (!esm || !EventStateManager::ManagesState(state)) { aRv.Throw(NS_ERROR_INVALID_ARG); return false; } bool result = esm->SetContentState(nullptr, state); if (aClearActiveDocument && state == NS_EVENT_STATE_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 EventStates return aElement.State().GetInternalValue(); } /* static */ already_AddRefed InspectorUtils::GetCleanComputedStyleForElement( dom::Element* aElement, nsAtom* aPseudo) { MOZ_ASSERT(aElement); Document* doc = aElement->GetComposedDoc(); if (!doc) { return nullptr; } PresShell* presShell = doc->GetPresShell(); 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, bool aSkipCollapsedWhitespace, nsLayoutUtils::UsedFontFaceList& aResult, ErrorResult& aRv) { nsresult rv = aRange.GetUsedFontFaces(aResult, aMaxRanges, aSkipCollapsedWhitespace); 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& aResult) { const auto kPseudoCount = static_cast(PseudoStyleType::CSSPseudoElementsEnd); for (size_t i = 0; i < kPseudoCount; ++i) { PseudoStyleType type = static_cast(i); if (nsCSSPseudoElements::IsEnabled(type, CSSEnabledState::ForAllContent)) { 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 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; nsContentUtils::NameSpaceManager()->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(); } 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()); } already_AddRefed InspectorUtils::GetOverflowingChildrenOfElement( GlobalObject& aGlobal, Element& aElement) { RefPtr list = new nsSimpleContentList(&aElement); nsIFrame* scrollableFrame = aElement.GetPrimaryFrame(); std::function GetOverflowingElement = [&](const nsIFrame* aFrame) { MOZ_ASSERT(aFrame, "we assume the passed-in frame is non-null"); for (const auto& childList : aFrame->ChildLists()) { for (const nsIFrame* child : childList.mList) { bool isBlameElem = true; // change this so that it becomes true iff // child is causing overflow. if (!isBlameElem) { GetOverflowingElement(child); } else { list->AppendElement(child->GetContent()); } } } }; if (scrollableFrame) { GetOverflowingElement(scrollableFrame); } return list.forget(); } } // namespace dom } // namespace mozilla